Monday, September 28, 2009

Using ISA as a Proxy Server in your ASP.NET apps

If the web server your web application or web service is running on is accessing a resource on the outside (a 3rd party web service for example) and it cannot, it could be that the web server your application is running on has an internal IP address. The issue is that it is not being instructed to use ISA as a proxy server. There are a couple of ways of doing this, but one way that has been proven to work is as follows.

The first thing is to change your app's web.config to specify the following section. This should be in a level within the .... Please note that case and spaces are important.


...


usesystemdefault = "false"
proxyaddress="http://YourISAServer:YourPort"
bypassonlocal="true"
/>


...


Once this has been done, you need to tell your Network Admins to setup a rule for allowing your application to pass through the ISA to outside resource. You can either supply them with the URLs of the outside resource or they can monitor the ISA server to see what is being denied. Once the ISA server is configured with the appropriate rules, then you should back in business.

Monday, August 3, 2009

Better SQL Server Security - How to protect your database

I have a database that was created for a custom application. The application may be broken up into multiple tiers or not. All interaction with the data is done using stored procedures. My problem is that I want to constrain my end users to accessing and executing the stored procedures through the custom application and nothing else. That means the user should not be able to go applications like Excel and make a connection to the database and execute one of the stored procedures. And keep in mind my users must authenticate and be authorized in SQL Server using their Windows identity for security and auditing purposes.

I have read posts on the Internet saying that Logon Triggers are a great way to prevent people from connecting to databases using “unapproved” software applications. The application would specify a Application Name in its connection string (Application Name=MyCustomApplication). A logon trigger can be written and the app_name can be evaluated and only allow the applications you ever wanted to connect to the SQL Server, thus eliminating Excel. While this is all fine and good, this isn’t 100% foolproof. Rouge EXEs could spoof an application name that is on the good list of EXEs and circumvent the logon trigger’s purpose.

I have also read that using a loginless account is one way to prevent users from accessing a database directly using their Windows identity. For example, let’s assume you have stored procedures written and you grant the loginless account rights to execute the procs. As long as the EXECUTE AS ‘loginless account’ is executed prior to executing the procs, then this works pretty well. The end users don’t have rights themselves to execute the procedures, but as the loginless used for the impersonation does. The problem I have with this approach is that you are requiring your application code to execute that EXECUTE AS statement before anything else is executed. And because the EXECUTE AS is scoped for the session/module, each time the application were to execute a procedure, it would have to first call the EXECUTE AS and do so over the wire (problem if you aren’t using SSL).

I also read about using signed procedures. In this case a certificate user is granted access to the tables themselves. And then the stored procedures are signed with the same certificate. Then the end users are permissioned to the stored procedures. But I don’t see how it is relevant because it seems this is addressing the issue of where ownership chaining is not possible. The users would still have the ability to execute the procedures from any application they wanted to.

So my problem is there is no good surefire way of allowing a Windows authenticated user to a database and only to the database if the user is using the application the database is intended for. There just seems to be no good way to handle this. There are of course things like Application Roles, but if you have spent any time with them, you know they are pretty much worthless in an enterprise class application. It seems in SQL 2005, Microsoft started to make improvements towards handling this issue, but it is still appears to be half baked.

Have you figured out something that I have yet to read about? Are you the guy/girl that has the answer? If you aren’t, have you thought about the possibility of users accessing your database other than using the application it was intended for?

To me, it almost seems as though the ultimate solution to this problem is to sign the application code and then in SQL Server, per database, say what certificate is allowed into this database. That could be the certificate of a user or a certificate of the application. By doing the user and application would people like DBAs could still get to databases using any application and end users could access the databases only with the “approved” applications.

Thoughts?

Monday, April 20, 2009

Global.asax Session_Start code not running

We were moving an ASP.NET 2.0 application from IIS 6 to IIS 7 and we ran into a problem. The code in the global.asax Session_Start was not running. However, this code had been running fine in every environment (development, testing, production) in IIS 6. Through a lot of research and trial and error, we figured out a solution to the problem. I’m not sure the problem was necessarily with IIS 7 because we saw posts on the Internet of the same symptoms on IIS 6.0. It should be noted that when the code is executed within VS, it ran fine too.

Our standard has been to use the Web Deployment Project for creating the compiled output that is then copied to a IIS server. Our standard has also been to merge all outputs to a single assembly. When this is done, all compiled code is put into a single DLL. If you use the global.asax, there is file called App_global.asax.compiled that is used to tell the runtime engine to reference the single DLL for the global.asax code. It would appear that when you have your project compiled into a single DLL, this problem manifests itself.

Our solution was to change the way the code was to be compiled. Rather than merging all outputs to a single assembly, we set it to merge all pages and control outputs to a single assembly. Do so still creates the App_global.asax.compiled file, but instead the compilation of the global.asax code is into its own DLL (App_global.asax.dll). By doing this the code in the global.asax started working.

Thursday, April 9, 2009

Job hangs when running SSIS package

I had a SSIS package that I modified and put out onto a development server. When I ran the existing job, the job would hang. Based on troubleshooting, it would appear the package would not even begin to start. When the package was executed from the development PC, it ran fine. When remoted into the SQL Server and ran the package in BIDS, it ran fine. However, when we ran in a command line the command that was in the job step, it would produce the following error. Note that the package would in fact run though under this method.

The file name is not valid. The file name is a device or contains invalid characters.

It turns out the problem was the fact that the package used a configuration file and the config file specified the location of where the checkpoint file should be written. The config file on the server had the checkpoint file location as the developer’s workstation and not of the location for the development environment. Upon changing the config file, the job would then run the package successfully. It would appear that when this type of error is encounter in a job, the job would hang and not handle the exception correctly.

Monday, March 30, 2009

Kerberos and SharePoint

Great link on Kerberos and SharePoint. Probably one of the best descriptions of how Kerberos really works.
http://www.sharepointjoel.com/Lists/Posts/Post.aspx?List=0cd1a63d%2D183c%2D4fc2%2D8320%2Dba5369008acb&ID=2

Monday, January 12, 2009

Quickly adding data to SharePoint List

I was working on a SharePoint project where I needed to quickly add 100’s of list items to a list. The list was not your ordinary list. The list had the following column types.

Text
Person or Group (Show Name field)
Person or Group (Show Account)
Yes/No (default Yes)
Lookup (based on a “division” list)

This list was to store a list of approvers for a workflow application I was working on. The Person columns were used to get the Display Name and the account name of the approvers (keep in mind we use Active Directory for our user store). The Yes/No column indicated whether or not to email the user. And the Lookup column was for identifying what division the person worked for. When it came time to populate this list, the users did not want to have to type in 100’s of people into this list. So they asked me to put them in. Lucky me.

Since I’m lazy, I wanted a quick way of doing this. First, I thought about doing it programmatically, but again, I’m lazy. So, there had to be a better way. Why can’t I just use SharePoint’s native functionality? Please know that what I’m about to describe took me the better part of half a day to figure out. But once I had the process down, it only takes a few minutes to accomplish what I needed to do.

Step 1: Clean Data
The users gave me a spreadsheet of the people they wanted added. And if your users are like mine, the spreadsheet contains data that is not very conducive to “importing”. It is always dirty and never contains all the data you really need. Since I knew that I would be adding users that are in our Active Directory, I did tell them I needed to have a unique way of identifying people. Luckily, we store our employee numbers in Active Directory, therefore, I was able to bounce the list against AD to obtain the approvers’ Display Name and Account ID. Hopefully, you have similar environment where you can easily do this. Once I had the AD data I needed, I continued in Excel and removed duplicates and so forth. Tip: learn about Excel’s VLOOKUP function. Its great!

Step 2: Build an “Import Spreadsheet”
Once I had clean data to work with, I turned to how I was going to get the data into the SharePoint list. I knew the list had the following columns.

Title
DisplayName
Account
EmailBackup
Division

So I went into Excel and created a sheet that had the same column names, mapping the clean data accordingly. For the Title column, I just entered “Approver” for all the rows. For the EmailBackup column, the users indicated which approvers should get an email. Therefore, using some Excel functions (IF and LEN functions), I populated this column with either a value of “Yes” or a blank value. For the Division column, I had a value of the Division for the approvers I was importing. The key here is to make sure the value matches that of one in the list the defines the lookup column. Once done, I had what looked like a clean and complete Excel spreadsheet that matched my SharePoint list.

Step 3: Importing data into SharePoint
When you look at a list, you have under the Actions menu a option called Edit in Datasheet. This is the key to doing what I needed to do quickly. But there are some problems with doing this. To actually get your data from the Excel spreadsheet into the list, all you really have to do is copy all columns/rows from the spreadsheet and paste them into the Edit in Datasheet view. Simple, simple! But, because of the column types, this becomes very problematic. Most notably, the Person or Group column is the biggest problem! When you try to paste, you may get an error similar to the following.

Cannot paste the copied data due to data type mismatches or invalid data. Some source rows which would have created new rows contained invalid values in required fields. These rows were skipped.

Say what? Okay, to save a lot of Googling, what this really means is you have data that cannot be used. And as I already clued you into, the columns that are defined as People or Group are the problem. The reason is because of how the Edit in Datasheet view works with these columns. I don’t have the 100% accurate explanation of this, but it appears that SharePoint gets from its “user store” all possible users and puts them into a dropdown list. If you are in the Edit in Datasheet view and dropdown a Person or Group column, you will see what I mean. So, what is happening is you have users in your spreadsheet data that is not in the SharePoint “user store”. Hence, invalid data.

Step 4: Fixing the Invalid Data error
So, how do you fix this? Easy, return to Excel for a quick moment. Create a new column in your spreadsheet. Using the CONCATENATE function, append a semicolon onto the end of either the user Display Name or Account (keep in mind, this is the data I got from AD). Now that you have a list of users with a semicolon at the end, copy that column and return to SharePoint.

Go to the Site Settings of the site where your list is and go to People and Groups. Create a new group, naming it so that it sticks out like a sore thumb. Now, add users to this group. In the Users/Group textbox, do a paste. What should happen is everything you copied from the spreadsheet (users with an semicolon appended to the end) should have pasted. Uncheck the “Send email” option and click Ok. The group should be populated with all of the users. Great. Now, delete the group.

This is the magic operation to get Edit in Datasheet People or Group dropdown list populated with the values you need!

Step 5: Finally, Populating the List
Return to your list and change back into Edit in Datasheet mode. Now you should be able to copy and paste your Excel data directly into the Datasheet, without error. It is as simple as that!

Monday, November 24, 2008

Validation of viewstate MAC failed as a result of 3.5 SP1

On the eve of installing the 3.5 SP1 framework on our production IIS servers, I uncovered a problem on our development server. Today I went to use a rather old application 2.0 on DHSIISD1 for a totally unrelated note and found that it was getting a runtime error. The error that was being produced was as follows.

Validation of viewstate MAC failed. If this application is hosted by a Web Farm or cluster, ensure that configuration specifies the same validationKey and validation algorithm. AutoGenerate cannot be used in a cluster.

After spending the past 4 hours trying to determine the root cause, I found that it has to do with the installation of the 3.5 SP1 Framework. How I determined it is what introduced the problem is because I could run the ASP.NET app without error locally on my PC (it also runs in production right now). I then installed the 3.5 SP1 Framework on my PC. After the installation the application running on my PC begin exhibiting the same symptoms. Then I began reviewing what changes were being addressed in the 3.5 SP1. There was a feature put in that allows you to set a form’s Action (HTMLForm.Action) during runtime for example. It is my belief that this is what broke the app. Actually, “broke” is rather subjective. There are a few things to make this stop working.

First, the page in question had controls that caused post backs to occur. The page also had a HTML form defined and the form has an action set.



Because of the HTMLForm.Action change, the form’s action was now being honored where as before, it was not, it was just ignored. As a result, when a control on the form caused a post back, the post back caused the framework to honor the form’s action and route to the confrimpage.aspx. As a result, that page tried to process the viewstate and it was not valid because it came from a different page. Thus, the error being generated. I have read people reporting this as a bug to Microsoft. In their case the symptoms were different. I added to the bug report our experience.

Now, you have to go back to the question of why did a 3.5 SP1 affect a 2.0 application. The bottom line is it should not have (the whole reasoning behind frameworks). But it would appear that 3.5 SP1 does more than just a service pack for the 3.5 framework. It also installs a SP2 for the 2.0 framework. If you look at the MSDN article for the HTMLForm.Action, it says that it is supported in 3.5 SP1, 3.0 SP2, 2.0 SP2. So, it looks to me that they also introduced this feature into the 2.0 SP2 as well. Which by the way is not a distributable you can just download.

Monday, October 6, 2008

Tuesday, September 9, 2008

Nice Free Geocoding Web Site

I needed to be able to quickly geocode some addresses and this site did just that AND it was free.

http://www.batchgeocode.com/

Wednesday, August 6, 2008

Method to pass multiple "parameters" to a stored procedure using XML

I want to share with you a technique I have devised. I had a case where there was a stored procedure in a database that allowed you to look something up based on a parameter. The problem I had was that I needed to do this lookup anywhere between 1 – 200 times depending on the circumstances. I really didn’t like the idea of calling the stored procedure 200 times, so I looked into another way. Here is what I came up with, use XML.

To start off with, if you have never messed with XML or even XML in relation to SQL Server, then you typically don’t think of using XML when we are talking about SQL Server. But, with advances in SQL Server and its usage of XML data, you might find more ways to take advantage of it. And this method does just that.

To begin this exercise, we need to build a few things to setup a very simple scenario. Granted, the solution I’m going to illustrate is overkill for this scenario, but I’ll explain how you could use this method in other places.

Create some objects
CREATE TABLE [dbo].[tblDepartment](
[DepartmentID] [int] IDENTITY(1,1) NOT NULL,
[DepartmentName] [varchar](50) NOT NULL
) ON [PRIMARY]

CREATE TABLE [dbo].[tblEmployee](
[EmployeeID] [int] IDENTITY(1,1) NOT NULL,
[FirstName] [varchar](50) NOT NULL,
[LastName] [varchar](50) NOT NULL,
[DepartmentID] [int] NOT NULL
) ON [PRIMARY]

Insert some data
INSERT INTO [dbo].[tblDepartment] ([DepartmentName]) VALUES ('Human Resources')
INSERT INTO [dbo].[tblDepartment] ([DepartmentName]) VALUES ('Sales')
INSERT INTO [dbo].[tblDepartment] ([DepartmentName]) VALUES ('Marketing')
INSERT INTO [dbo].[tblDepartment] ([DepartmentName]) VALUES ('Communications')
INSERT INTO [dbo].[tblEmployee] ([FirstName],[LastName],[DepartmentID]) VALUES('John', 'Smith', 2)
INSERT INTO [dbo].[tblEmployee] ([FirstName],[LastName],[DepartmentID]) VALUES('Nancy', 'Holder', 3)
INSERT INTO [dbo].[tblEmployee] ([FirstName],[LastName],[DepartmentID]) VALUES('Craig', 'Jones', 1)
INSERT INTO [dbo].[tblEmployee] ([FirstName],[LastName],[DepartmentID]) VALUES('Susan', 'Henderson', 1)
INSERT INTO [dbo].[tblEmployee] ([FirstName],[LastName],[DepartmentID]) VALUES('Robert', 'Craft', 4)
INSERT INTO [dbo].[tblEmployee] ([FirstName],[LastName],[DepartmentID]) VALUES('Jimmy', 'Foldgers', 2)
INSERT INTO [dbo].[tblEmployee] ([FirstName],[LastName],[DepartmentID]) VALUES('Max', 'Eilliot', 3)

Now that we have a couple of tables and some data, let’s talk about what we are wanting to do. Let’s say we have a stored procedure that will allow you to pass it a DepartmentID and it will return all of the employees who work in that department. But let’s say you need to get this information for all departments. In the example data above, you would have to call this stored procedure 4 times and then combine the 4 separate results into one result set. Seems inefficient to me. There has to be a better and easier way. This is where the XML part comes into play.

To start off, we need to first create a stored procedure that will give me what I want to look up. Consider the following.
CREATE PROCEDURE dbo.spGetDepartmentIDs
@RtnDeptID VARCHAR(MAX) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
WITH MyCTE (I) AS
(
SELECT DepartmentID
FROM tblDepartment
FOR XML RAW('item'), ROOT('items')
)

SELECT @RtnDeptID = MyCTE.[I] FROM MyCTE

SET NOCOUNT OFF;
END

So, what does spGetDepartmentIDs do? It returns all the department IDs as XML. So if I execute the following I will get some neat results.
DECLARE @xmlDeptID AS VARCHAR(max)
EXEC dbo.spGetDepartmentIDs @xmlDeptID OUTPUT
SELECT @xmlDeptID AS MyDeptIDs

Results (formatted for easy reading)







You are probably wondering why I’m using a CTE in this procedure. The reason is so that I can get back a defined column name. If you just issue the SELECT statement, the column name returned is nasty and you may not want to deal with it. So now you asking yourself, “So what, you can get back an XML string?” Now the cool part.

What if I pass this XML string onto a procedure that had a incoming parameter of an XML type? And then what if I could join to that XML data? It would be excellent, right? Here is how.
CREATE PROCEDURE dbo.spGetEmployeesForDeptByXML
@xmlDeptString XML
AS
BEGIN
SET NOCOUNT ON;

SELECT E.DepartmentID, E.EmployeeID, E.FirstName, E.LastName
FROM dbo.tblEmployee E
INNER JOIN @xmlDeptString.nodes('//items/item') AS x(item)
ON E.DepartmentID = x.item.value('@DepartmentID[1]', 'INT')

SET NOCOUNT OFF;
END
GO

Now, if I execute the following bit of code, I have my solution.
DECLARE @xmlDeptID AS VARCHAR(max)
EXEC dbo.spGetDepartmentIDs @xmlDeptID OUTPUT
EXEC spGetEmployeesForDeptByXML @xmlDeptID

Again, this is a very simple scenario and there is a much better way of doing this scenario. But, consider this technique if you were integrating with another system. Let’s say you needed to pass a list of some IDs to another system’s stored procedure and have it return the data back to you. Or say you need to insert data into another system. You could build an XML string that you could pass in one call. Again, you could call the other system’s stored procedure x times, or you would have a similar implementation as I have described above.

In terms of performance, I’m sure there is some gotchas with this. In my case, I was dealing with a small amount of data (200 records or less). So the XML string was small. I’m sure if you passed a string that had 1000 items, the join being done could be expensive. Therefore, use this method at your own risk and test it well. To learn more about the XML-type code used above, check out BOL or Google it. It is very powerful.

Wednesday, July 16, 2008

Running SSIS Package gives: error failed validation and returned validation status "VS_NEEDSNEWMETADATA"

When running a SSIS package with a data pump task, if you receive an error similar to failed validation and returned validation status "VS_NEEDSNEWMETADATA" the problem could be that the security for the account running the package is not setup for the object being called within the data pump task source task. In the case this was found, the data pump was calling a stored proc in another database and that stored proc joined to data in another databases. The AD account for the system that was running the package needed to have rights to execute the store proc in the first database as well as the rights on the view the stored proc was joining to. The best way to troubleshoot this problem is to create a new package and have it execute a SQL task that calls the stored proc. Then run the package on the server and this will help pin point where the problem occurs.

Thursday, July 3, 2008

Get list of AD group members

Quick and dirty way to do this.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.DirectoryServices;
namespace ADTest
{
public partial class Form2 : Form
{
public const string adpath = "LDAP://domain.com/";
public Form2()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Boolean iresult;
iresult = GetGroupMembers(textBox2.Text);
}
public static DirectoryEntry GetDirectoryEntry()
{
DirectoryEntry de = new DirectoryEntry();
de.Path = adpath;
de.AuthenticationType = AuthenticationTypes.Secure;
return de;
}
public bool GetGroupMembers(string GroupName)
{
DirectoryEntry de = GetDirectoryEntry();
DirectorySearcher ds = new DirectorySearcher(de);
ds.Filter = "(&(objectClass=group)(cn=" + GroupName + "))";
SearchResult results = ds.FindOne();
if (results != null)
{
DirectoryEntry deGroup = new DirectoryEntry(results.Path);
System.DirectoryServices.PropertyCollection pcoll = deGroup.Properties;
int n = pcoll["member"].Count;
textBox1.Text = n.ToString();
for (int l = 0; l < n; l++)
{
DirectoryEntry deUser = new DirectoryEntry(adpath + "/" + pcoll["member"][l].ToString());
richTextBox1.Text = richTextBox1.Text + GetProperty(deUser,"givenName") + " " + GetProperty(deUser,"sn") + "\n";
deUser.Close();
}
deGroup.Close();
de.Close();
return true;
}
else
{
de.Close();
return false;
}
}
public static string GetProperty(DirectoryEntry oDE, string PropertyName)
{
if (oDE.Properties.Contains(PropertyName))
{
return oDE.Properties[PropertyName][0].ToString();
}
else
{
return string.Empty;
}
}
}
}

Thursday, June 26, 2008

Problem Connecting to SQL 2005 Named Instance with Vista

If you are running Vista and you are using Management Studio or an application that is connecting to a SQL 2005 named instance server, you will receive the error.

Login timeout expiredAn error has occurred while establishing a connection to the server. When connecting to SQL Server 2005, this failure may be caused by the fact that under the default settings SQL Server does not allow remote connections.SQL Network Interfaces: Error Locating Server/Instance Specified [xFFFFFFFF]. (Microsoft SQL Native Client)
This problem results in that in Vista, you must specify the port number of the instance you are connecting to. So rather than connecting to Server\Instance you need to connect using Server\Instance,port.

Friday, June 20, 2008

Using Access 2007 as a conduit to SharePoint list data

I stumbled onto something today that I don’t know what to think of and thought I would kick it out to the world for comment. I was grappling with the idea of using “data” in lists that exist in a site collection. By using, I mean using in the since of reporting on it, integrating it with other data sources (SQL Server, web services, etc), etc. I knew that Access 2007 had native capability to treat SharePoint lists like a linked table. So, I thought to myself that if I can create a linked table to a SharePoint list and then I can use the Access 2007 database as a data source for applications other (ASP.Net, SSIS, SQL Server, SRS, etc) then would that work. Indeed it did! I first created two custom lists in SharePoint, each of them being in totally different web sites within the same site collection. Then I created an Access 2007 database and created linked tables to these lists. The first test was to see if I could join these lists together in an Access query. Yes. Then I thought, okay, can I create a SQL Server Integration Services (SSIS) package to work “directly” with the SharePoint data, but use the Access database as a conduit. So, I setup a connection to the Access database using the OLE DB Office 12 driver. Then I created a SQL Task to insert into the Access linked table that was linked to one of the lists. I ran the SSIS and by golly it inserted into the list just fine. I then wrote a transformation task that joined SQL Server data in a SQL database to the list data in SharePoint in order to insert data into another SQL Database. And that worked.

So, is this good or bad? I can’t see this being very scaleable, but using Access 2007 as a conduit into SharePoint data is a lot easier than working with the SharePoint API or web services and dealing with CAML. It seems to be that Microsoft should introduce a SharePoint data provider that can be used in SQL Server or .NET so that you can treat SharePoint lists just like tables without all of the headaches. What are your thoughts?

Problem with Site Navigation in SharePoint

When I tried to do something in a site's Navigation, after clicking the OK button, I would get this SharePoint error.

Microsoft.SharePoint.Library.SPRequestInternalClass.MoveNavigationNode

It turns out this is because a Heading somehow has an invalid link. The only way I found to fix this is to go into SharePoint Designer and change to the Navigation pane and delete the offending headings. You can determine the offending heading by clicking on them in the site. If you get a page cannot be displayed error, then you have found the right heading. In SPD delete these. And then you will have to recreate the Navigation for these and their sublinks.

Wednesday, April 30, 2008

Friday, April 25, 2008

Mapping a folder to the GAC

Here is the command to map the GAC folder to a drive

Start->Run->cmd
subst g: c:windowsassembly (with the backslashes in the right spot)

Open G: drive in Explorer

Thursday, April 24, 2008

Weird GAC issue

I was doing a WSS Application Page (first time to do this) and found a weird behavior. When I went to test the page, I would get an "File not found" WSS error. I would go back to my code and review it and make a couple of changes and redeploy it. Then it would work. Then I would make a simple change, redeploy it, and then it would break. It turns out it was a problem with the registering of the assembly into the GAC. It would fail to register every other time. I caught this by mapping a drive to the GAC folder and watching it. When the page would work, the DLL would be copied to the correct folder. But the next time it would be copied to some kind of temp folder. Then if I looked in the VS output window, I would see that it was failing because of access denied when it went to register it. I haven't dug any further into this, but for right now, when I make a change, I just make sure the DLL is put into the GAC correctly before I test it.

Scan directly into SharePoint

I recommended this product to my client. It allows you to directly scan a document from a desktop into SharePoint. It works really well and is easy to setup. And for the price, it isn't a bad deal.

https://www.darkblueduck.com/Default.aspx

Friday, February 29, 2008

easyPDF

While looking into the ability to convert an InfoPath form to PDF, I came across a product called easyPDF from BCL Technologies. They have an SDK that allows you to work with their "virtual" printer. At the current moment, I'm doing a POC to see how I can use this product in a SharePoint/Infopath/Workflow project. At the current time, it looks promising.