Oct 21 2008

Retrieve All Documents from SharePoint

I needed a way to quickly retrieve all documents from SharePoint. You can specify which files to retrieve by changing the WHERE clause in the SQL query.  Otherwise, just pass it a path for the output files and set the connection string for your SharePoint content database.

Props to Michael O’Donovan for his helpful article on recovering SharePoint files.

public static void GetDocs(string path)
{
    SqlConnection _conn = new SqlConnection(Settings.Default.conn);
 
    string sql = "SELECT AllDocs.LeafName, AllDocs.DirName, AllDocStreams.[Content] ";
    sql += "FROM AllDocs INNER JOIN AllDocStreams ON AllDocStreams.Id = AllDocs.Id AND AllDocs.[Level] = AllDocStreams.[Level] ";
    sql += "WHERE AllDocs.IsCurrentVersion = 1 AND right(AllDocs.LeafName, 4) in ('.doc','.pdf','.xls','.csv','.ppt','docx','xlsx','pptx','.rtf','.txt','.xps','.mdb','.msg','.wmv','.xsn','.vsd')";
 
    SqlCommand getEmp = new SqlCommand(sql, _conn);
 
    FileStream fs;                          // Writes the BLOB to a file (*.bmp).
    BinaryWriter bw;                        // Streams the BLOB to the FileStream object.
    int bufferSize = 100;                   // Size of the BLOB buffer.
    byte[] outbyte = new byte[bufferSize];  // The BLOB byte[] buffer to be filled by GetBytes.
    long retval;                            // The bytes returned from GetBytes.
    long startIndex = 0;                    // The starting position in the BLOB output.
    string filename = string.Empty;         // The orignial filename.
    string outfile = string.Empty;
    string subpath = string.Empty;
    int i = 0;
 
    // Open the connection and read data into the DataReader.
    _conn.Open();
    SqlDataReader myReader = getEmp.ExecuteReader(CommandBehavior.SequentialAccess);
 
    while (myReader.Read())
    {
        i++;
 
        // Get the filename and build the file path for the output files.
        filename = myReader.GetString(0);
        subpath = myReader.GetString(1).Replace("/", "\\") + "\\";
        outfile = path + subpath + filename;
 
        if (!Directory.Exists(path + subpath))
        {
            Directory.CreateDirectory(path + subpath);
        }
 
        // To prevent overwriting files.
        if (File.Exists(outfile))
        {
            outfile = path + subpath + "_" + i.ToString() + filename;
        }
 
        // Create a file to hold the output.
        fs = new FileStream(outfile, FileMode.Create, FileAccess.Write);
        bw = new BinaryWriter(fs);
 
        // Reset the starting byte for the new BLOB.
        startIndex = 0;
 
        // Read the bytes into outbyte[] and retain the number of bytes returned.
        retval = myReader.GetBytes(2, startIndex, outbyte, 0, bufferSize);
 
        // Output status messages
        string msg = String.Format("{0} ", subpath + filename); 
        Console.Write(Right(msg,48));
 
        while (retval == bufferSize)
        {
            bw.Write(outbyte);
            bw.Flush();
 
            // Reposition the start index to the end of the last buffer and fill the buffer.
            startIndex += bufferSize;
            retval = myReader.GetBytes(2, startIndex, outbyte, 0, bufferSize);
        }
 
        // Write the remaining buffer.
        bw.Write(outbyte, 0, (int)retval);
        bw.Flush();
 
        // Close the output file.
        bw.Close();
        fs.Close();
        Console.Write(" [Complete]");
        Console.WriteLine();
    }
 
    // Close the reader and the connection.
    myReader.Close();
    _conn.Close();
}


Apr 19 2008

Enable Self-Service user in Salesforce.com using C#

I won’t walk through connecting to the Salesforce.com api because that is well documented.  One thing I didn’t find documented was how to enable self-service for a Salesforce contact.  Well here it is.

First 3 methods used by the main EnableSelfService() method:

   1: /// <summary>
   2: /// Retrieves a Contact object for the specified contactId
   3: /// </summary>
   4: /// <param name="contactId">contactId</param>
   5: /// <returns>Contact object (containing contact name, phone and email address</returns>
   6: public Contact GetContact(string contactId)
   7: {
   8:     //Verify that we are already authenticated, if not
   9:     //call the login function to do so
  10:     if (!loggedIn)
  11:     {
  12:         if (!Login())
  13:             return null;
  14:     }
  15:     // Invoke retrieve call and save results in an array of SObjects
  16:     sObject[] records = binding.retrieve("FirstName, LastName, Email, Phone, MySequenom_Enabled_On__c, AccountId, Account.Name, Account.BillingState", "Contact", new String[] { contactId });
  17:     Contact contact = null;
  18:     // Iterate through the results
  19:     if (records!=null && records.Length > 0)
  20:     {
  21:         contact = (Contact)records[0];
  22:     }
  23:     return contact;
  24: }

 

   1: /// <summary>
   2: /// Find existing self-service user by email address
   3: /// </summary>
   4: /// <param name="email">Email address</param>
   5: /// <returns>Self-Service userid</returns>
   6: private string GetExistingSelfServiceUserId(string email)
   7: {
   8:     //Verify that we are already authenticated, if not
   9:     //call the login function to do so
  10:     if (!loggedIn)
  11:     {
  12:         if (!Login())
  13:             return null;
  14:     }
  15:     QueryResult qr = binding.query(string.Format("Select Id from SelfServiceUser where Email = '{0}'", email));
  16:     string ssuId = null;
  17:     if (qr.size != 0)
  18:     {
  19:         ssuId = qr.records[0].Id;
  20:     }
  21:     return ssuId;
  22: } 

 

Reset a self-service user’s password:

   1: /// <summary>
   2: /// Sets the password for the given userid/selfserviceuserid
   3: /// </summary>
   4: /// <param name="id">userId or selfserviceuserId</param>
   5: /// <returns>auto generated password.</returns>
   6: public string ResetPassword(string id)
   7: {
   8:     //Verify that we are already authenticated, if not
   9:     //call the login function to do so
  10:     if (!loggedIn)
  11:     {
  12:         if (!Login())
  13:             return null;
  14:     }
  15:     string newPassword = StringHelper.GetPassword(6, true, false, false);
  16:     // Invoke setPassword call and save results in SetPasswordResult 
  17:     SetPasswordResult spr = binding.setPassword(id, newPassword);
  18:     // returns the generated password 
  19:     return newPassword;
  20: }    

 

And finally enable self-service:

   1: /// <summary>
   2: /// Enables self-service for the specified contactId
   3: /// </summary>
   4: /// <param name="contact">Contact object</param>
   5: /// <returns>password for the newly created Self-Service user.</returns>
   6: public string EnableSelfService(Contact contact)
   7: {
   8:     string email, firstName, lastName, contactId;
   9:     //Verify that we are already authenticated, if not
  10:     //call the login function to do so
  11:     if (!loggedIn)
  12:     {
  13:         if (!Login())
  14:             return null;
  15:     }
  16:     email = contact.Email;
  17:     firstName = contact.FirstName;
  18:     lastName = contact.LastName;
  19:  
  20:     // I needed to make some changes to the contact first. So update the contact...
  21:     contactId = contact.Id;
  22:     contact = new Contact();
  23:     contact.Id = contactId;
  24:     contact.Enable_MySequenom__c = true;
  25:     contact.Enable_MySequenom__cSpecified = true;
  26:     contact.MySequenom_Enabled_On__c = DateTime.Now;
  27:     contact.MySequenom_Enabled_On__cSpecified = true;
  28:  
  29:     // then save the contact.
  30:     try
  31:     {
  32:         SaveResult[] srContact = binding.update(new sObject[] { contact });
  33:     }
  34:     catch (Exception e)
  35:     {
  36:         throw new ApplicationException("Unable to update contact record for contactid: " + contactId + Environment.NewLine + e.ToString());
  37:     }
  38:  
  39:     // open the contact back up.
  40:     contact = GetContact(contactId);
  41:  
  42:     // check to see if a self-service user already exists for this contact.
  43:     string ssuId = GetExistingSelfServiceUserId(contact.Email);
  44:  
  45:     // Self-Service user already exists, just reset password.
  46:     if (ssuId != null){
  47:         return ResetPassword(ssuId);
  48:     }
  49:  
  50:     // Create the new self-service user.
  51:     SelfServiceUser ssu = new SelfServiceUser();
  52:     ssu.ContactId = contactId;
  53:     ssu.Email = email;
  54:     ssu.FirstName = firstName;
  55:     ssu.LastName = lastName;
  56:     ssu.Username = contact.Email;
  57:     ssu.IsActive = true;
  58:     ssu.IsActiveSpecified = true;
  59:     ssu.LanguageLocaleKey = "en_US";
  60:     ssu.LocaleSidKey = "en_US";
  61:     ssu.TimeZoneSidKey = "America/Los_Angeles";
  62:  
  63:     // Invoke the create call, passing in the SelfServiceUser properties
  64:     // and saving the results in a SaveResult object
  65:     SaveResult[] srSsu;
  66:     try
  67:     {
  68:         srSsu = binding.create(new sObject[] { ssu });
  69:     }
  70:     catch (Exception e)
  71:     {
  72:         throw new ApplicationException("Unable to create self-service user for contactid: " + contactId + Environment.NewLine + e.ToString());
  73:     }
  74:  
  75:     // Access the new ID and generate a new password
  76:     ssuId = srSsu[0].id;
  77:     string newPassword = null;
  78:     if (ssuId != null && ssuId != string.Empty)
  79:     {
  80:         newPassword = ResetPassword(ssuId);
  81:     }
  82:     return newPassword;
  83: }


Apr 18 2008

Working with Users and Groups in SharePoint 2007

As I am about to show you, one of the easier things to do in SharePoint (programmatically) is that add a user to a group and add a user or group to a site.

I’ll be using the SharePoint web service to do this, so first we need to setup the web reference.  Add a web reference to your project using the URL: http://[sitename]/_vti_bin/usergroup.asmx (of course you’ll substitute your site name).

Then we need to setup the web service object (I named my web reference AdminService):

   1: static AdminService.UserGroup userService = new AdminService.UserGroup();
   2: userService.Credentials = System.Net.CredentialCache.DefaultCredentials;

NextI have an enum, class and method to make it easy to remember and work with the different permission types.

   1: /// <summary>
   2: /// Enumeration representing the various permission levels in SharePoint
   3: /// </summary>
   4: public enum SharePointPermissionType
   5: {
   6:     /// <summary>
   7:     /// Full Control - Has full control.
   8:     /// </summary>
   9:     [Description("Full Control")]
  10:     FullControl, 
  11:     /// <summary>
  12:     /// Design - Can view, add, update, delete, approve, and customize.
  13:     /// </summary>
  14:     Design,
  15:     /// <summary>
  16:     /// Manage Hierarchy - Can create sites and edit pages, list items, and 
  17:     /// documents.
  18:     /// </summary>
  19:     [Description("Manage Hierarchy")]
  20:     ManageHierarchy,
  21:     /// <summary>
  22:     ///  Approve - Can edit and approve pages, list items, and documents. 
  23:     /// </summary>
  24:     Approve, 
  25:     /// <summary>
  26:     /// Contribute - Can view, add, update, and delete.
  27:     /// </summary>
  28:     Contribute, 
  29:     /// <summary>
  30:     /// Read - Can view only.
  31:     /// </summary>
  32:     Read,
  33:     /// <summary>
  34:     /// Restricted Read - Can view pages and documents, but cannot view historical 
  35:     /// versions or review user rights information.
  36:     /// </summary>
  37:     [Description("Restricted Read")]
  38:     RestrictedRead,
  39:     /// <summary>
  40:     /// View Only - Members of this group can view pages, list items, and documents. 
  41:     /// If the document has a server-side file handler available, they can only view 
  42:     /// the document using the server-side file handler.
  43:     /// </summary>
  44:     [Description("View Only")]
  45:     ViewOnly
  46: }

   1: /// <summary>
   2: /// This internal class is used in conjunction with the SharePointPermissionType enum.  
   3: /// It allows for the definition of values as strings.
   4: /// </summary>
   5: class Description : Attribute 
   6: { 
   7:     public string Text; 
   8:     public Description(string text) 
   9:     { 
  10:         Text = text; 
  11:     } 
  12: }

Place this within the class where you’re using the enum:

   1: /// <summary>
   2: /// Used in conjunction with the SharePointPermissionType enum. It returns the value 
   3: /// as a string.
   4: /// </summary>
   5: /// <param name="en">SharePointPermissionType</param>
   6: /// <returns>String representation of a SharePointPermissionType</returns>
   7: private string GetDescription(Enum en)
   8: {
   9:     Type type = en.GetType();
  10:     MemberInfo[] memInfo = type.GetMember(en.ToString());
  11:     if (memInfo != null && memInfo.Length > 0)
  12:     {
  13:         object[] attrs = memInfo[0].GetCustomAttributes(typeof(Description), false);
  14:         if (attrs != null && attrs.Length > 0)
  15:             return ((Description)attrs[0]).Text;
  16:     }
  17:     return en.ToString();
  18: }

 

Finally the meat & potatoes…

Add a user to a SharePoint Group (SPGroup):

   1: /// <summary>
   2: /// Adds a user to a group
   3: /// </summary>
   4: /// <param name="userName">username of user to add</param>
   5: /// <param name="groupName">group name</param>
   6: /// <param name="userEmail">user's email address</param>
   7: public void AddUserToGroup(string userName, string groupName, string userEmail)
   8: {
   9:     try { userService.AddUserToGroup(groupName, userName.Replace("CSUpmProvider:", ""), userName, userEmail, ""); }
  10:     catch { }
  11: }

 

Add a user to a SharePoint Site (SPSite):

   1: /// <summary>
   2: /// Adds a user to a site
   3: /// </summary>
   4: /// <param name="userName">username of the user to add</param>
   5: /// <param name="siteUrl">url of the site</param>
   6: /// <param name="permission">permission level to grant the group on the site.</param>
   7: public void AddUserToSite(string userName, string siteUrl, SharePointPermissionType permission)
   8: {
   9:     OpenSite();
  10:     SPWeb web = _site.OpenWeb(siteUrl);
  11:     SPMember member = web.ParentWeb.ParentWeb.AllUsers[userName];
  12:     if (member != null)
  13:     {
  14:         SPRoleAssignment roleAssignment = new SPRoleAssignment((SPPrincipal)member);
  15:         SPRoleDefinitionBindingCollection roleDefBindings = roleAssignment.RoleDefinitionBindings;
  16:         roleDefBindings.Add(web.RoleDefinitions[GetDescription(permission)]);
  17:         web.RoleAssignments.Add(roleAssignment);
  18:     }
  19:     web.Close();
  20:     web.Dispose();
  21:     CloseSite();
  22: }

 

Add a Group to a Site:

   1: /// <summary>
   2: /// Adds a group to a site
   3: /// </summary>
   4: /// <param name="group">SharePoint group</param>
   5: /// <param name="siteUrl">url of the site.</param>
   6: /// <param name="permission">permission level to grant the group on the site.</param>
   7: public void AddGroupToSite(SPGroup group, string siteUrl, SharePointPermissionType permission)
   8: {
   9:     OpenSite();
  10:     SPWeb web = _site.OpenWeb(siteUrl);
  11:     SPMember member = web.SiteGroups[group.Name];
  12:  
  13:     if (member != null)
  14:     {
  15:         SPRoleAssignment roleAssignment = new SPRoleAssignment((SPPrincipal)member);
  16:         SPRoleDefinitionBindingCollection roleDefBindings = roleAssignment.RoleDefinitionBindings;
  17:         roleDefBindings.Add(web.RoleDefinitions[GetDescription(permission)]);
  18:         web.RoleAssignments.Add(roleAssignment);
  19:         web.Update();
  20:     }
  21:     web.Close();
  22:     web.Dispose();
  23:     CloseSite();
  24: }

 

Finally you’ll notice I keep using the methods OpenSite() and CloseSite().  These simply open and close the SPSite _site object:

   1: /// <summary>
   2: /// Opens a SharePointSite
   3: /// </summary>
   4: /// <returns>SPSite object representing the base site collection</returns>
   5: private SPSite OpenSite()
   6: {
   7:     CloseSite();
   8:     _absoluteUrl = "http://myspsite.com";
   9:     _site = new SPSite(_absoluteUrl);
  10:     return _site;
  11: }
  12:  
  13: /// <summary>
  14: /// Close opened site
  15: /// </summary>
  16: private void CloseSite()
  17: {
  18:     if (_site != null)
  19:     {
  20:         _site.Close();
  21:         _site.Dispose();
  22:         _site = null;
  23:     }
  24: }


Apr 17 2008

Create a new alert for a SharePoint list

A code snippet for creating a new alert for a SharePoint list.  The OpenSite() method simply creates an SPSite object named which I’ve named _site.

Make sure that your alertUser has permissions to the list. Also you need to set the property AlwaysNotify = false.  For some reason it doesn’t seem to work if you don’t set this property.

public void CreateAlert(SPList list, SPUser alertUser, string alertTitle, bool notify)

        {

            OpenSite();

            try

            {

                SPWeb web = _site.AllWebs[list.ParentWebUrl];

                SPAlertCollection alerts = web.Alerts;

 

                // get the lists alert template and apply that to the new alert

                SPAlertTemplate alertTemplate = list.AlertTemplate;

                alertTemplate.Name = list.AlertTemplate.Name;

 

                // setup the alert

                SPAlert alert = alerts.Add();

                alert.AlertType = SPAlertType.List;

                alert.AlertTemplate = alertTemplate;

                alert.Title = alertTitle;

                alert.EventType = SPEventType.Add;

                alert.AlertFrequency = SPAlertFrequency.Immediate;

                // make sure to set this to false. Don't know why but doesn't seem to work if you don't.

                alert.AlwaysNotify = false;

                alert.User = alertUser;

                alert.List = list;

                alert.Update(notify);

                

                // cleanup

                web.Close();

                web.Dispose();

            }

            catch

            {

                Console.WriteLine("Unable to create alert");

            }

            CloseSite();

        }


Apr 16 2008

Creating and deploying a custom site definition for MOSS 2007

I’ve spent the last couple weeks trying to create a custom site definition for SharePoint 2007. If you’re unsure of what a custom site definition is and how it differs from a site template, there is a good MSDN article which explains both.

I found a bunch of good resources out there that describe the steps in creating a custom site definition, but none gave me everything I needed. I will give a brief rundown of the steps that I followed, and provide links to the articles that I referenced.

  1. Download WSPBuilder and the WSPBuilder Extensions for Visual Studio. (I believe you need to install the Visual Studio SDK prior to installing the extensions)
  2. In Visual Studio create a new WSPBuilder Project.
  1. I don’t know if it is supposed to be this way or just a problem that I was having, but it did not create the entire 12 hive folder structure like I thought it would. To fix this, I ran WSPBuilder.exe –createfolder true from the command line, and then added the resulting folder structure to my Visual Studio project.
  • Then follow these steps: Creating a Custom WSS Site Definition [2007]
    1. In the example from that article, the author uses a folder named SampleMPS. In your Visual Studio project, you will be using the \12\TEMPLATE\ directory.
  • Now, I wanted to associate a custom masterpage to my site definition. The steps I followed to do that are outlined here: http://statto1974.wordpress.com/2007/04/30/using-a-custom-master-page-in-a-site-definition/. Put your master file in the \12\TEMPLATE\SiteTemplates\v2DefaultOrg\ (where v2DefaultOrg is whatever you named your site definition) directory, beside the default.aspx file. Below is a screenshot of my project folder structure. You’ll notice the eight folders in my FEATURES folder. The first is a feature that, when activated, associates a site with a new custom masterpage (read more). The remaining seven are custom list definitions. I have two site definitions in my project 1) v2DefaultOrg and 2) v2DefaultUser.
    clip_image002
  • Once you have everything setup as you like. Right-click on the project and choose WSPBuilder –> Deploy. Then go into SharePoint and test out your Site Definition by creating a new site using it.
  • When you’re ready and it’s time to package it up and deploy it on your production SharePoint farm. Right-click on the project again and choose WSPBuilder –> Create deployment folder.
  • Then go to your project BIN folder and you’ll see a deploy folder. Inside this folder you should see a setup.exe and a .wsp file. The .wsp file is the Solution Package, and the setup.exe is from the SharePoint Solution Installer project on CodePlex. You’ll need to create and modify a configuration file as described on the SharePoint Solution Installer home page.
  • Then you simply need to run the setup.exe and the project should install on your SharePoint farm.
  • As I’ve found creating and deploying a custom site definition can be frustrating. Hopefully the information I’ve provided will be of help to someone.


    Feb 6 2008

    SubSonic: All Your Database Are Belong To Us

    I’m a lazy programmer. Don’t get me wrong, I think that’s a good thing. I believe it drives me to come up with innovative solutions for problems that I’m too lazy to deal with on a regular basis. This frees me up to work on the things that I enjoy, while spending less time on the tedious.

    Lately I’ve turned to CMS systems like Kentico, Drupal, and Joomla when I’m creating websites, because I can spend more time on the design and layout and less time developing the backend.

    Sometimes, I want more control than a CMS allows, and I want to write more code. In the past I’ve written my own data access layer, and in most cases that works just fine. However, i “need to spend more time with [my] friends, family, dog, bird, cat…” so I started looking for solutions…and I found several:

    To be fair, I only tried the first three because they’re open source (free), and in the end, SubSonic met all my needs. It is easy to use. All you really need to do, it setup the database connection string in the app.config/web.config file, run a macro, and sit back and drink your coffee. In the end you’re presented with a couple classes for each table in your database.

    Then creating a table record is as easy as:


    user = new UserObject(1);
    user.FirstName = "Adam";
    user.LastName = "Conde";
    user.EmailAddress = "myemail@something.com";
    user.Save();

    Querying is similarly easy.
    Check it out for yourself: SubSonic: All Your Database Are Belong To Us