Creating a SharePoint 2010 Business Intelligence VM Image (using SQL Server 2008 R2 CTP)

Introduction

SharePoint is becoming more and more prominent as the delivery mechanism for BI solutions within the Microsoft stack. The SharePoint 2010 BETA along with SQL Server 2008 R2 further strengthen the overall business intelligence capabilities and functionality possible in an organization.

I have a VM image to ramp up on SharePoint 2010 itself but in order to expand into all of the new BI features I wanted to create a new one (from scratch) with the latest SQL Server 2008 R2 CTP. So I did! It was a very tedious process but I found a guide to assist me.

I used the wonderful instructions laid out here: http://www.sharepointdevwiki.com/display/spadmin2010/Installing+SharePoint+2010+Public+Beta

However, since I was using SQL Server 2008 R2 and wanted to configure it for full business intelligence usage, I needed to modify the steps slightly. This post will outline those steps and show any updated walkthroughs.

These instructions/steps are needed only when using SQL Server 2008 R2 CTP as there are several installation issues when integrating with SharePoint 2010. Hopefully in the release these will be corrected.


Installation Steps

1) Follow Steps 00, 0b, 0c and 01 from the SharePoint Dev Wiki guide

2) Instead of step 02, we will install SQL Server 2008 R2 CTP but that comes later! DO NOT INSTALL SQL Server 2008 yet.

3) Steps 3-9 can now be skipped as everything there will be (or has been) installed with Windows Server 2008 R2, SQL 2008 R2, and SharePoint Pre-Reqs.

4) Do not even look at Step 10 - skip to 10b and 10c. I happened to disregard the notes about using the Administrator account for everything and ran into an issue at the end. You may want to look at Step 15 and review the supporting links (as well as the supporting links within those pages). Key thing is to not setup that sp_admin account for this VM.

Also, I decided after the fourth time to actually install the hotfix first (10c), reboot, then install the SharePoint Pre-Reqs (10b), and reboot again. Take the time now because if something fails you could waste hours or days going through the same steps again and again.

5) Perform Step 11 - DO NOT RUN THE CONFIGURATION WIZARD - UNCHECK THE CHECKBOX. Click on Close. The next step here will perform the rest of the configuration.

6) Install SQL Server Analysis Services Integration Mode - (see walkthrough below).

7) Perform Steps 13 and 14
It does take a few minutes to start the User Profile service and to run the synchronization. I did not see anything in the Running Jobs (the first time but the second time I did).


8) Perform Step 15 - before you click on the User Profile Service Application, select it first by click to the right of the link. Then on the top ribbon, click on Administrators (see screens below). The Administrator account should be in there automatically but only the "Retrieve People Data..." permission is selected. I selected Full Control as the TechNet links from those supporting links discussed about the account having full control.




I thought the Profile load was finished and found 0 users but it just took some time to complete. The status said it was done but apparently something was running in the background.

9) Now go back to the SQL Server 2008 R2 installation and install the default instance with Reporting Services (Integrated) and Analysis Services (native). See walkthrough below. 


10) Configure Reporting Services - Integrated Mode.  See walkthrough below.

11) Optional: Install Visual Studio 2010

12) Optional: Install Office 2010 BETA. Include the Visual Studio Tools for Office (VSTO).





SQL Server 2008 R2 - Analysis Services Integrated Mode Installation and Configuration Walkthrough


























Click the "Use the same account..." button and enter the Administrator credentials. Click OK.



Click on "Add Current User". I was paranoid the "umpteenth" time and actually added the Local Service, Local System, and Network Service accounts in attempts to prevent the time-out issue (the issue is described below).








VERY IMPORTANT!
Since this is a domain controller, a time out issue will occur as explained here: http://powerpivotgeek.com/2009/11/17/installing-powerpivot-for-sharepoint-on-a-domain-controller/
You need to monitor the services in Server Manager - Services  (while the installation is running) and change the SQL Server Analysis Services (GeminiBI) Service back to the administrator account and make sure it is started. This needs to be corrected as soon as it happens.

You do have time however. The service won't even be listed until it gets installed. The switch occurs about midway through the ASSPIInstallFarmAction. When the install is up to this point, you can see the service listed. I just kept hitting the Refresh and I did see it change eventually. Don't forget to Start the service after you change the Log On back to the Administrator account.





Click Install!



If this step does not complete successfully, you might as well delete the VM and start over. It took me three days to get this right. Possible errors are: 1) timeout, 2) Object Not Set to Instance of an Object, and 3) Could not load or find assembly Microsoft.AnaysisServices.SharePoint.Integrated. There could be others but I wouldn't even bother trying to uninstall and re-install - I tried too many times only to find the best way was to just start over from scratch. Believe me, it will save you hours.


After completion. Go to Central Admin and click on the Configuration Wizards:

Select the Farm Configuration.

Click Next




Use the Existing Managed Account and make sure the Administrator is selected. Click Next.


This process already created a root site collection (I was a little upset about that). So we can skip this part. Click Skip.



Click Finish!






Default Instance - SQL Server 2008 R2 Installation Walkthrough (after Gemini Instance Install)
(For Step 9 above)












Select All. Click Next.

































SQL Server 2008 R2 - Reporting Services Configuration (Integrated Mode) Walkthrough
(For Step 10 above)







If you selected Integrated mode during installation, many of these settings are already in place. Go through each screen and make sure there is no warning symbol. Click Apply on the screens that show settings that haven't been set. The walkthrough may go through some additional details that aren't needed for your case. These mimic the configuration if a mode (native or integrated) was not selected.


 





If the database is not set, click on Change Database.

 











Click Exit.


Download and run the Reporting Services Add-In for SharePoint 2010:
http://www.microsoft.com/downloads/details.aspx?FamilyID=16bb10f9-3acc-4551-bacc-bdd266da1d45&displaylang=en












Click Finish.

Go to Central Admin, and look under General Application Settings:



Click on Reporting Services Integration.




Click Close.
 

SQL Server 2008 Reporting Services Integration with SharePoint 2010

I have been working with a Virtual Machine running SharePoint 2010. It has SQL Server 2008 Enterprise Edition but it did not have SSRS installed. I therefore downloaded the 64-Bit version of SQL Server Enterprise to install the extras.

I went through the process and when I saw some options disabled I figured something was wrong:



The SharePoint options were disabled. I couldn't select Native or Integrated mode here. I realized that it was looking for MOSS 2007.

I therefore realized that SQL Server 2008 R2 probably has the SharePoint 2010 awareness. I immediately downloaded the SQL Server 2008 R2 Enterprise Evaluation and installed it on the VM. This time I was able to configure Reporting Services properly:







After downloading and running through the installation of SQL Server 2008 R2, I realized that the process for installing Reporting Services is essentially the same as before (with SQL Server 2008 making it a little easier than SQL Server 2005). This means that you still need to install the SharePoint Add-in as well.

So what you need are both parts:

SQL Server 2008 R2 November CTP

http://www.microsoft.com/sqlserver/2008/en/us/r2.aspx
(I used the download from MSDN but it is publicly available using the above link)

SQL Server 2008 R2 RS Addin for SharePoint

http://www.microsoft.com/downloads/details.aspx?FamilyID=16bb10f9-3acc-4551-bacc-bdd266da1d45&displaylang=en

After you install the Add-In for Sharepoint, new options appear in Central Admin (just like in MOSS 2007):



Clicking on the Reporting Services Integration allows you to complete the integration process:



But it appears now can specify which site collections to deploy and activate the features. I had a few other site collections on the VM but under the /sites/ managed path. I am therefore thinking that it is really asking you to select the Root Site Collection (so if I had a another web application with a site collection, that would be shown in the selection box as well).

After successfully integrating, a Reporting Services option appears under the site collection site settings:




That's about as far as I have gotten so far. I did, however, wanted to check BIDS to see what Data Sources were available. I had a hunch that a SharePoint list would now be an integrated option (instead of having to create an extension as I have done before in SQL 2005). I was right:



I am pretty sure that wasn't in there before I installed R2.

So overall in SharePoint 2010 it's the same process to install Reporting Services in Integrated Mode but with the SQL Server 2008 R2 installs.



SSRS 2008 Pie Chart Formatting

I was involved with a client where after building the warehouse to host the data, one of user deliverables was delivering a SSRS report that contained many different types of charts. One of the charts that had a wrinkle to it was a Pie Chart that broke down customer dollars by category and then comparing that customer to its industry to see the percentages of where the client's customer income was in relation to the rest of the industry that the customer belonged. For this report, the customer was a parameter and it would display the appropriate information based upon the customer selection by my client.

Building two Pie Charts and having them side by side on the SSRS Report to display these percentages and dollars was a great way to compare the results but it had a couple of formatting issues that I would like to share.

For the requirement, I needed both charts to have the same colors representing the same category in each chart. I could have hard coded the categories but then the report would not have been very flexible if new categories were introduced into the data down the line. To solve this problem, I had to display all of the category types (simple left join) in the legend for the industry and the customer charts. Originally, I only showed those categories that each belonged to but if a customer only was involved in a subset of what the total industry would be involved in, the color coding of the charts would not be in synch. The first requirement was now solved using the left join but now I had a new problem.

Adding the left join now meant that I was retrieving all categories that could have a percentage of 0 since not all customers were involved in every category that the customer's industry was mapped. When displaying that on the chart and trying to show percentages and dollar amounts, if there were multiple categories that a customer did not have, all of the 0 percents and dollars became one garbled mess of characters.

Luckily, in SSRS 2008, one can change the formatting of the Pie Chart's Label Series using Keywords and combining that with a numeric string format. The keywords can be found in the Adding Chart Keywords section in the Formatting Data Points on a Chart article on MSDN, the numeric strings can be found at the Custom Numeric Format String section on MSDN.

To go to the Label formats, right click on the Labels within the chart and select the Series Label Properties:

SSRS_Pie_Chart_Label_Series

The properties will display and in the General option, implement the Chart Keywords and a custom numeric format string combination:

SSRS_Pie_Chart_Label_Data

For the example, I implemented the following format: #PERCENT{#%;;""} #VALY{$#,###;;""}. Notice, after the keyword, the format is laid out and the Section Separator ";" splits the following types of values into "POSTIVE;NEGATIVE;0" and what format to apply to each type of numeric value.

For the 0 value, I put in an empty string and therefore the combining of empty categories did not display in the Pie Chart itself but the category would still show up in the Legend.

The Pie Chart also has the capability to group percentages together below a certain threshold (percents or dollars) to then allow for a custom title for that category. However, for the customer vs the industry scenario and the color coordination of the legends, this avenue would not apply since I was not guaranteed the same combination of categories for the industry would be setup for the customer, which was the point of the chart in the first place.

Business Intelligence Seminar Series

Do more with what you already have. Leverage the products you already own and the data your systems produce to gain operational efficiencies and improve your company's business intelligence capabilities!

Baltimore
Tuesday, Nov. 3 (9:00 a.m. – 11:30 a.m. EST)
Hyatt Place
4730 Painters Mill Road
Owings Mills, MD 21117
Register

Reston, VA
Wednesday, Nov. 18 (9:00 a.m. – 11:30 a.m. EST)
Microsoft Corporation
12012 Sunset Hills Rd.
Reston, VA 20190
Register

Washington, D.C.
Tuesday, Dec. 1 (9:00 a.m. – 11:30 a.m. EST)
Microsoft Corporation
5404 Wisconsin Ave.
Chevy Chase, MD 20815
Register

Philadelphia
Wednesday, Dec. 2 (9:00 a.m. – 11:30 a.m. EST)
Philadelphia Marriott West
111 Crawford Avenue
West Conshohocken, PA 19428
Register

Hosting SQL Server on VMWare

One of our clients was investigating the logistics of running SQL Server on VMWare. In discussions with Microsoft, the response included four (4) important links. I personally have seen various SQL Server performance issues when virtualizing especially in a SharePoint environment and I would never trust that configuration. There have been many enhancements with SQL Server 2005 and now SQL Server 2008 in order to consolidate servers and take advantage of reducing hardware needs and license costs. A good SQL Server 2008 consolidation white paper can be found here.

Here is the info from the Microsoft technician:

If you are running ESX V3.5 the latest hotfixes from VMware to disable write-caching are included in update3 and update 4.


1. The Microsoft Virtual Server support policy


2. The VMware knowledge base article that you can contact VMware about for hosting SQL Server


3. This article that explains the issue and discusses the problem domain


4. Here is an presentation from VMWare about how to disable the disk write issue.

To MERGE or not to MERGE in SQL Server 2008

I was reviewing some demo material for the MERGE statement that Jim Pletscher put together. It was a standard scenario where we wanted to UPDATE, DELETE, or INSERT rows from a source table to a target table. A general overview of this is available here.

Inserting is harmless but what if sometimes we don't want to update or delete something from our target table?

My first step was to add a new column to the target table. I named this column "DoNotTouch" and made it a bit defaulting to zero (0). I then set this value to one (1) on one of the target table rows.

So now how do we tell the MERGE statement to not touch anything where this bit is flipped on? You cannot use a WHERE clause in the MERGE statement. The answer is within the WHEN clause of the MERGE statement. All you need to do is add an additional condition to each WHEN for the update and delete operations:

(TargetTable is the name of the target table and SourceTable is the name of the source table)

WHEN MATCHED AND TargetTable.DoNotTouch<> 1 THEN


               UPDATE SET TargetTable.FirstName = SourceTable.firstname,

                                        TargetTable.LastName = SourceTable.lastname

WHEN NOT MATCHED BY SOURCE AND TargetTable.DoNotTouch<> 1 THEN
DELETE


So that worked! It did not touch the row where I set the DoNotTouch flag to one (1).

Introduction to Change Data Capture

The Change Data Capture feature is a new feature in SQL Server 2008 that allows processes to
quickly and easily identify changes to a table in SQL Server 2008. It primarily works by watching the SQL server transaction logs, and running processes that pull changes from the logs and moving them into tables.

Change Data Capture Setup

Enabling Change Data Capture is a two step process. First, you must enable it at the database level, then you must enable it for each table for which changes must be recorded.

The first step in setting up Change Data Capture is to enable the option at the Database Level. The easiest way to do this is to use the system stored procedure sys.sp_cdc_enable_db.
Simply execute this stored procedure from the desired database, and it will enable change data capture for the database. This stored procedure requires sysadmin privilege in order to execute it.

(click to enlarge)














Once it is enabled at the database level, the Capture Change Data option must be enabled for each table for which changes should be captured. Use the sys.sp_cdc_enable_table stored procedure to enable the feature, passing the table name as an argument in order to enable the feature. By default, all of the columns in the source table are identified as captured columns. If only a subset of columns need to be tracked, such as for privacy or performance reasons, use the @captured_column_list parameter to specify the subset of columns.

(click to enlarge)




















Once Change Data Capture (CDC) is enabled for a table, several things occur. First, an associated capture instance is created to support the collection of the change data in the source table. The capture instance consists of a change table and up to two query functions.

All inserts, updates, and deletes to the source table are stored in the change table. The two query functions are specific to the source table, and can be used to fetch changes from the change table.

The first five columns of a change data capture change table are metadata columns. These provide additional information that is relevant to the recorded change. The remaining columns mirror the identified captured columns from the source table in name. These columns hold the captured column data that is gathered from the source table.

Each insert or delete operation that is applied to a source table appears as a single row within the change table. The data columns of the row that results from an insert operation contain the column values after the insert. The data columns of the row that results from a delete operation contain the column values before the delete. An update operation requires one row entry to identify the column values before the update, and a second row entry to identify the column values after the update.

In additon to the Change instance, two SQL Agent Jobs are created: one that is used to populate the database change tables, and one that is responsible for change table cleanup. Both jobs consist of a single step that runs a Transact-SQL command. The Transact-SQL command that is invoked is a change data capture defined stored procedure that implements the logic of the job.

(click to enlarge)






















The jobs are created when the first table of the database is enabled for change data capture. The Cleanup Job is always created. The capture job will only be created if there are no existing transactional publications for the database.

Using Change Capture Data


Change data is made available to change data capture prcoesses via the two functions that are created when the change instance is created. Both functions require Log Sequence Numbers (LSNs) to help define the date range of change data to return in the resultset.


The function cdc.fn_cdc_get_all_changes_ returns all changes that occurred for the specified interval. Entries are always returned sorted, first by the transaction commit LSN of the change, and then by a value that sequences the change within its transaction.

The function cdc.fn_cdc_get_net_changes_ returns one change per modified source table row. If more than one change is logged for the row during the specified time period, the column values will reflect the final contents of the row.

Several functions are provided to help determine appropriate LSN values for use in querying the change data capture functions. For example, the functions sys.fn_cdc_map_time_to_lsn and sys.fn_cdc_map_lsn_to_time are available to help place LSN values on a conventional timeline.

To illustrate, insert a single record into sample table created earlier, then update the columns.




































Now we can use the cdc.fn_cdc_get_all_changes to set how the change table is populated:

















Note that the resulting change table contains four records, with the source table columns populated with the table data surrrounding the change. The first record reflects the initial insert. The 2nd and 3rd records reflect the update (a delete followed by new insert), while the fourth record reflects the final delete.
Note that running the cdc.fn_cdc_get_net_changes returns only a single row, which represents the newest version of any modified row. This procedure will only return one change table record per source record changed, regardless of how many changes were made to the source record.



























Business Intelligence and Silverlight

Looking forward to an upcoming book on two of my favorite areas of interest: Business Intelligence and Silverlight.



SQL Server 2005 vs 2008 Query Performance

I was recently working on a transactional web application for a client. This web application had some "operational" type reports included within it that required aggregations of, what will eventually be, millions of rows of data. This meant I'd be writing queries directly against the transactional tables doing the aggregations (SUMs and COUNTs mostly) "on demand".

Note: In more heavily used applications, aggregating as much data as we were on demand might not be acceptable, but in this case we were measuring in transactions "per minute" and "per hour" rather then "per second", so we did not anticipate any blocking of our queries by database writes.

These queries were going to be used on the landing page for web application seen by every employee within the company. The landing page receives 20,000 hits per day over roughly 17 hours of operation a day. This is about 19 hits per minute or 1 hit every 3 seconds. Not all that much compared to many applications I've seen, but definitely enough to warrant some sort of load/performance testing under a larger data load.

I set up some SQL scripts to, somewhat randomly, load a data sample that was representative of about 2 years worth of data. My initial query times were awful, and as I suspected, there was some room for improvement. I did some refactoring (with some help from a friend) and had things running at about 1-2 seconds per query. I thought there was still some room for improvement, but this was within an acceptable range.

Thinking things were fine, I promoted my code from my local development environment to our integration environment. I then ran my data load script to test in that environment, and suprisingly, was seeing query times in the 20-25 second range. WHAT?!? I just tuned things. After some tinkering, and reloading of data, I was seeing the inconsistencies in my query times. Sometimes in the 20-25 second range, other times in the 3-5 second range. What was going on?

My initial thought was that there was something different in the query processing between my local development environment (SQL Server 2008 Developer Edition) and the integration environment (SQL Server 2005 Standard edition). Some quick searches found that I might be using the Star Join Query Optimization available in 2008 Developer/Enterprise editions. There are ways that I could have confirmed this assumption, but I did not spend the time to do so. Regardless if I was or wasn’t using this optimaztion, I still didn’t want to take a chance that there might have been something else in the query processor that was making things more “efficient” on 2008 Developer vs 2005 Standard. I thought it’d be more reliable if I just set up a more representative environment and did my testing/tuning there.

I built a 2005 Standard virtual machine, built my application, loaded sample data, and ran the queries. Started seeing query times similar to our integration environment. OK, so it was reproduceable. Now let's tune it. I went straight to an indexed view I was using for the query, made a minor tweak. As I ran the script to rebuild the indexed view, it hit me. I had just loaded 2 years worth of data, and had done no database maintenance that would "normally" be done gradually, such as rebuilding indexes and defragging the hard drive. I rebuilt my indexes, defragged the drive, and sure enough my query times were back down in the 1-2 second range.

After tweaking just a bit more by including my numeric columns used in my query aggregations in an index, I redeployed to our integration environment and finally started getting consistent and performant results.

A few important take-aways here...

  1. In order to truly gauge/test performance, perform maintenance on your development/test environment just as you would in a “normal” environment, especially after large data loads. That is rebuild your indexes, defrag the hard drive, etc.
  2. Always (or to the extent possible) develop and/or test on machines that are representative of the environment you are actually going to deploy to, especially with regards to versions/editions of applications being used. There are many features within SQL Server that are only available in the Enterprise Edition of the product, including some query optimizations. I usually do this, but for a long period of time on my current project, the client couldn’t give a definitive answer on what our environments would look like, so we continued development with what we originally started with (which was no longer valid). Once I built a more representative environment of what I was actually deploying to, then deployed my application and test data there, I was able to troubleshoot and fix the issue more quickly.

Excel Services Part Deux - Excel Web Services

In an earlier blog entry, I discussed in very broad terms Microsoft Excel Services – a server side calculation engine that can be leveraged for SharePoint Business Intelligence solutions. Excel Services, you may recall, is divided into three components; Excel Calculation Services (ECS), Excel Web Access (EWA), and Excel Web Services (EWS). Excel Web Services allows access to the Excel Calculation Engine via Web Service calls, and is today’s focus.

Excel Web Services utilizes SOAP over http and acts as an interface between custom client programs and Excel Services. In order to call the service, it is necessary to establish a reference to the Excel Web Services WSDL, accessed through ExcelService.asmx?wsdl. However, if you are writing code within SharePoint Services (for example, developing custom web parts), Microsoft recommends linking directly to Microsoft.Office.Excel.Server.WebServices.dll and making local calls rather than using loop-back SOAP calls.

EWS - What is it good for?
Absolutely nothing (say it again…)? No, there are a number of practical examples where utilizing Excel Web Services comes in handy. One common scenario occurs when a company’s business logic is embedded in Excel spreadsheets. These spreadsheets can be maintained on a corporate server, and custom applications developed to utilize the logic in the spreadsheet without having to recode it in a “conventional” programming language. This also means that the spreadsheet logic can be maintained by business Subject Matter Experts (SMEs) who are comfortable working in Excel. Custom code can be developed to set values to cells and ranges, process the workbook, and retrieve calculated values (or even the entire workbook).

Coding Steps 101
Dealing with EWS is relatively straightforward, and numerous code examples can be found in the literature and on the web, such as here: http://blogs.msdn.com/excel/archive/2005/11/21/495454.aspx


Typical steps involved in coding against EWS are as follows:


1. Instantiate the web service via creation of ExcelService object
2. Set the URL via ExcelWebService property
3. Set the credentials via Credentials property. Default credentials (using applications own credentials) can be used by setting this property to System.Net.CredentialCache.DefaultCredentials.
4. Start the Session by calling OpenWorkbook() method
5. Set Cell Parameters via SetCell() method
6. Calculate Spreadsheet (if workbook not set to auto-recalculate) via Calculate() or CalculateWorkbook() method
7. Retrieve the Results via GetCell() method
8. Close Session via call to CloseWorkbook() method


Other methods are available as well, including the ability to set a range of cells and read an entire workbook into memory.

Business Example
An RDA client manages a customer support system that utilizes a custom authorization engine that controls which support agents have access to specific functionality within the application. For example, agents that support a specific product line do not have access to workflows supporting other product lines, and only agents of a certain tier level have the rights to issue refunds to customers. Although this authorization data is stored in a relational database, an Excel Spreadsheet on a SharePoint site is used by both the business and application support personnel to maintain and modify this information. When changes are made, the spreadsheet is loaded into the relational database through custom software.

This approach has a number of benefits:


1. SharePoint is used as a means of version control, to maintain a historical record of changes made.
2. Anyone with access to the SharePoint site can view the details surrounding the current authorization hierarchy, without the need for special software or reports.
3. The spreadsheet itself contains business logic to help organize the information and maintain data integrity.
4. The spreadsheet is easy to use and modify for subject matter experts who may not have a programming background.

However, as initially conceived there are some difficulties associated with this approach:


1. Making changes and deploying to the relational database involves a number of time-intensive manual steps, including downloading the spreadsheet, exporting to XML, applying an XSLT transform, and executing a custom application.
2. In addition to the labor involved, the manual steps are prone to error.
3. In the event that the changes made are not what were desired, either through human error or a change in business strategy, rollback of the changes can be difficult.

Excel Web Services can provide an ideal platform to address these shortcomings. A SharePoint web part or other custom application can be developed that, utilizing Excel Services, can recalculate and read the latest spreadsheet data, generate the necessary output, and load the relational database in (from an end users point of view) a single step. No modifications to the existing spreadsheet logic would be necessary, and the business users can use and maintain the spreadsheet as before. SharePoint security can be leveraged to restrict access to the Web Part. In addition, custom rollback functionality can be added via code.

Finally…

As indicated in my last installment, relying on business logic residing in an Excel spreadsheet may not be considered “ideal” from an architectural standpoint – but is often a necessity when practical considerations such as time, budget, staff experience, and maintainability come into play. Excel Web Services provides an IT organization a valuable tool to leverage that platform for maximum benefit.