Wednesday, March 21, 2007

Visual Studio WSS Extensions, Agile and sharepoint-Scanning displayed in the Canberra User Group

I would like to thank Neil Hadley from Dark Blue Duck for coming to the user group yesterday and teaching me and the other people there some things about the released Visual Studio WSS Extensions.

He also gave a very informative case study on developing a sharepoint solution using agile methodology which was very eye opening. I really agree with everything he showed us - and it fits perfectly with my own presentation from a month ago about the good and bad practices in sharepoint development.

Finally, Neil showed us a preview of a scanning solution his company is about to release which looks like a very nice solution. When it is available to purchase (he also promised a trial version) I will post a link to it.

I am a certified Technology Specialist for MOSS!


In January I wrote a tip about the MOSS exams, and just this morning I got an email from Microsoft that I passed one of them - the big one!


I am now officially a MCTS in Microsoft Office SharePoint Server 2007 Application Development.

I did a little search on the internet, and on my blog list, and it seems to me that no one else wrote about getting this certification, so (in danger of making a fool out of myself) I will risk calling myself the first certified MOSS developer!




I am still waiting to hear the results of the WSS development certification - I would love to be the first with that one as well...






Tuesday, March 20, 2007

Print list feature improved!

That is why I like open source. Scot Hiller who started an open source project for free sharepoint features took my "Print List" feature and improved it, and published it in codeplex. I wish the referance to me was clearer (it just says "the author" and codeplex doesnt support putting in links in the description! ha!) but I know that Scot did his best with the codeplex interface.
So run to codeplex and download the print list from there. And if you have the time and knowledge - contribute to the project!

Thursday, March 15, 2007

Object model code and stsadm fail with "Access Denied"

This frustrated me when I was trying to use my utility pack on a server that I had local admin rights on. Every time the utility pack tried to do something in code that involved sharepoint (like SPSite site = new SPSite(path)) I go an access denied error, even though I was an admin of the site as well!
I then noticed that it's not just the utility pack that is not working. Backup would fail with similar errors, and stsadm would not install my solutions.Access Denied! Access Denied! Access Denied!
arrrrgghhhhh!

After some research I came to the conclusion that direct SQL permissions are required for the user who is running the sharepoint code. For web parts, event handlers and workflows this is usually the application pool user who is given permissions on the SQL databases directly when you create the web application. But if you want to run a console application or a windows application like mine, or if you want to do actions like backup or installations using stsadm, you still need to have those rights.

My theory got confirmed a couple of days ago in the msdn forums, in a post where someone asked about a similar problem and I replied with what I knew. Pat Miller from Microsoft replied to affirm my theory and said "If you run code in the context of the web site, the account that the web server is running has access to the SQL box. When the system needs access to SQL, it can revert to the process account (which has rights). However if you are running from a command line, there is no underlying account that can be reverted to. Instead, the account running the operation has to have access to the appropriate resources. ".

I asked him about least privileges needed by an account to run server code, but he couldn't give me specifics, so this is still open. I worked with my DBA and he concluded that "Datareader/datawriter provide the ability to access/update any table in the DB. It appears that the scripts also call on stored procs and this is why the db_owner rights were required.". He adds that we could try to trim down the permissions for the user and find the least privileges, but we both don't have the time to do that right now.

I wonder if anyone else knows what minimum set of rights are required to run code on the server (and stsadm of course).

Event handler impersonation - continued

In his comment to my latest article about impersonation in event handlers, Anders Rask said an interesting thing. He suggested that I use the "RunWithElevatedPrivileges" function of the SPSecurity class to run the piece of code as the account running the application pool.
He was kind enough to leave a code sample, and it is clear he knows what he is talking about (check out his article about impersontion in a web part for the 2003 version. Link requires registration to MSD2D.com - a site I don't believe still exists, seeing it's horrible interface).

To answer Mr. Rask, I will explain why I am not using this "simple" impersonation:

  1. Because I don't always want to impersonate that system account. My clients are very security sensitive, and system accounts get as little permissions as possible. If my event handler needs to do things that require access to systems that the account is not supposed to touch, we need to impersonate another account.
  2. Because it doesn't work!
    Ok, maybe I am over reacting, but the fact of the matter is that even though the "SPSecurity.RunWithElevatedPrivileges" seems to switch to the system account, I actually get 'access denied' when I try to access resources that the account should have permissions to access.
    Example: in my event handler I want to write to a log file. Because the data written to the log is very sensitive, I want to put the log file in a secure file system folder on the server. for this example, "c:\temp" is secure enough. I made the application pool account a server administrator (more, much more than you need to write a file in c:\temp) and still I get permission denied when the event handler is trying to open the stream.
    Using the impersonation example of Victor Vogelpoel [Macaw] I had no problem specifying the actuall application pool account and it's password and the log file is writing!
    Even more, I check the "web.CurrentUser" value and that returns "SHAREPOINT\system" and not the user name for the application pool (I guess that is why it fails accessing the file system). It also didn't "see" the document library that I had removed all user access from (except the SHAREPOINT\system and the application pool account).

So my analysis say - the "RunWithElevatedPrivileges" may be useful in webparts (I never tried), but looks to be useless in Event Handlers (at least for my purposes).
I would welcome any feedback!

Tuesday, March 13, 2007

Impersonation in Event Handlers

I have been using impersonation in event handlers, and thought I should share with you my prefered method.

I am working with the wonderful example of Victor & Julien Lepine of how to do impersonation.
However, there is one thing to beware of. In event handlers, the best way to get the referance to the list item, the list and the web site the event has happened in is use the "properties" object that we recieve as parameter:

public override void ItemUpdated(SPItemEventProperties properties)
{
    
ImpersonationUtility imp = ImpersonationUtility.ImpersonateAdmin();
    
using (SPWeb web = properties.OpenWeb())
    {
        
//my code here 
        string username = web.CurrentUser.Name;
    }
    imp.Undo();
    
base.ItemUpdated(properties);
}

Looks ok? the answer is no. Even though it seems we are impersonating administrator in the line before we are opening the SPWeb object, it is still opened with the credentials of the user who triggered the event. Why? because the "SPItemEventProperties properties " object was created with those credentials.

What to do?:
I came up with a roundabout way of getting the work done:

ImpersonationUtility imp = null;
try
{
    imp = 
ImpersonationUtility.ImpersonateAdmin();
    
using (SPWeb webOrigUser = properties.OpenWeb())
    {
        
//get the token for the impersonation user (this will get the user that the ImpersonationUtility is using)
        SPUserToken token = webOrigUser.AllUsers[WindowsIdentity.GetCurrent().Name].UserToken;
        
using (SPSite site = new SPSite(properties.SiteId, token))
        {
            
using (SPWeb web = site.OpenWeb(properties.RelativeWebUrl))
            {
                
            }
        }
    }
}
catch{}

What does that code do? We impersonate (as usual) and then get the SPWeb object from the properties. We use it to get an "SPUserToken" object for the impersonation user, and then we are opening an SPSite object with that token. The SPWeb object we open from that SPSite (note how I give it the relative url to open) is opened with the correct credentials of the impersonating user.

This solved all my impersonation problems. How about you?

Move file in event handler causes "No item exists" Error

I have been playing around with an event handler that moves files after they have been uploaded to a document library. This is useful in scenarios where you want people to upload files (or post infopath forms) to a library, then analyze the meta data and move it to another library automatically based on what the user chose in the meta data.

A cool example of how we may use this is an infopath form where the user's details are getting pulled from a database, including his "state" attribute. The event handler, when seeing the user is from a specific state, moves the document to a document library where it will be accessabile only by the people in the same state. This way alerts can be set be state and a seperate workflow can be developed for each state and so on.


The code is actually easy. I don't use the SPFile.MoveTo() function since it doesn't support moving to other sites, and that may be required. So I wrote a function that gets the source file (SPFile) and the target folder (SPFolder), copies the file by reading it's stream and then deletes the source file. This ofcourse can be improved in the future.



private static void MoveFile(SPFile fileToMove, SPFolder targetFolder)

{

    

        
Stream s = fileToMove.OpenBinaryStream();

        targetFolder.Files.Add(fileToMove.Name, s);

        targetFolder.Update();

        fileToMove.Delete();

       

}




The problem I discovered that even though I was running the code in the ItemAdded event, the code would run when the file is uploaded. This causes a big problem, since sharepoint 2007, after uploading the file checks if there are any properties (meta data) it needs to ask the user for, and redirects him to the meta data entry screen. But my event handler already deleted the file!

So every time the user uploads a file, he will see:
"No item exists at http://site/web/library/Forms/EditForm.aspx?Mode=Upload&CheckInComment=&ID=23&RootFolder=/web/library&Source=http://site/library/Forms/view.aspx. It may have been deleted or renamed by another user."





I have tried using the "ItemCheckedIn" event instead, with no luck - it is not called when a file is uploaded. It looks like the sequence of events is:


  • ItemAdding

  • ItemAdded

  • ItemUpdating (this is after the user enters the metadata)

  • ItemUpdated



So from this we can conclude (and I tested to confirm) that if you want to move a file on upload, do it in the ItemUpdated event, or the ItemUpdating event.

The problem is, if the user presses "Cancel" in the metadata window, the file will remain in the library, since the "Updating" events didn't trigger.

What to do? I honestly don't know. It seems we cannot develop an event handler that moves files automatically and will trap all documents uploaded, without causing errors on the screen for the user.






Sunday, March 11, 2007

Fernando Felman has a blog!

Everyone please update your blog roll, and add the following blog:Fernando Felman's Cottleston Pie blog.
Currently he is writing on .NET development (interesting stuff) and maybe in the future he will get sucked back into sharepoint.
Those of you who doesn't know Fernando, he is an excelent developer, and we have been working together for 4 years now (I brought him to Australia with me, in a big suit case).
So if you are interested in developing in .NET, take a look.

Monday, March 05, 2007

Sharepoint Tips And Tricks now has a domain!

Thanks to earning from various ad services I have been able to buy a domain, and after some false starts (and the help of Chuck from blogger status for real beta and from my friend Mundeep Rehill) I got the setup right, and you should now see that the site address is www.sharepoint-tips.com (I wanted www.spstips.com but someone parked on it, probably waiting for me to buy the domain from him).

So, please update your bookmarks and links to use this more friendly address.

[Update]
To answer Oskar's cynical comment (Don't worry Oskar - I enjoy such comments, and you do have a point), I cannot justify waiting for ad earning to get a domain (8$). The truth is that I am looking for a hosting company that will allow me to open a decent sharepoint site, and pay them with the rest of the earnings.
The trouble is - I cannot find a decent one. I want a place where I can install my web parts on the server, and that is a big huge problem for web hosts. The only way I can do it is get my own server in a host, and that costs a lot of $$$.
If anyone has any suggestions to my dilemma, please let me know...

Monday, February 26, 2007

How to add a site column to a list by code

A colleague just asked me how to add a site column defined in the root web to a list somewhere in the site, so here is the small snippet:

private void AddSiteColumnToList(string sitePath, string listName, string columnName)
{
     using (SPSite site = new SPSite(sitePath))
     {
          using (SPWeb web = site.OpenWeb())
          {
                //Since the site column is on the root web,
                //we need to get the root web
                //If it was'nt on the root web, but instead on the same site as the 
                //list, we could have just used the "web" object
                using (SPWeb root = site.RootWeb)
                {
                     //get the site column from the root web
                     SPField fld = root.Fields[columnName];
                     //get the list from the site
                     SPList list = web.Lists[listName];
                     //add the column
                     list.Fields.Add(fld);
                     //update the list
                     list.Update();
                 }
           }
      }
}