Microsoft PlayGround

A Playground for Microsoft Technologies

Using SPSecurity.RunWithElevatedPriviledges()

The last time I have been receiving questions about using SPSecurity.RunWithElevatedPriviledges in a SharePoint webpart. This made me think and I thought by myself lets write a short post with some examples about this subject.

When you want to build a webpart that will be used by anonymous or read only users you often have to use RunWithElevatedPriviledges because a lot of objects in a SharePoint site require more rights. If you use RunWithElevatedPriviledges you will run a particular action under a account that has more rights in SharePoint.

I hear you asking under what account do we run this particular code then? The answer is the application pool account of your web application.

Now we know that we can write some examples to see what will work. Let’s begin with an example how you should not use RunWithElevatedPriviledges.

 SPWeb web = SPContext.Current.Web;

 SPSecurity.RunWithElevatedPrivileges(delegate()
 {
    SPList list = web.Lists["Test"];
 });

In this example we get the current web object from the SPContext. This means we have got a web object that runs under the account that we are logged in with. After we have got the web object we use RunWithElevatedPriviledges because read only users can not access the list.

If we would run this code in a webpart we will see an access denied message, all because we use a web object that is retrieved with our current context.

Let’s take a look at an example how you could do it the right way (this is the way I mostly use RunWithElevatedPriviledges):

 Guid webID = SPContext.Current.Web.ID;
 Guid siteID = SPContext.Current.Site.ID;

 SPSecurity.RunWithElevatedPrivileges(delegate()
 {
    using (SPSite site = new SPSite(siteID))
    {
       using(SPWeb web = site.AllWebs[webID])
       {
          SPList list = web.Lists["Test"];
       }
    }
 });

In this example you can see that we first retrieve the ids of the site collection and the web so that we know which site object and web object we want to work with.

We then use RunWithElevatedPriviledges and retrieve the SPSite and SPWeb within the RunWithElevatedPriviledges. These objects will then run under the account of the application pool.

I hope this post will you guys out there and please fill free to leave comments.

ASP:Menu in IE8

Since a few days I have installed Internet Explorer 8 on my computer and I was curious how it would display certain sites. Till a few hours ago everything looked nice.

When navigating to an internet site we had just developed for one of our clients I noticed a strange thing. On the default page we added a custom control that inherits from the ASP Menu. The hover of that menu wasn’t working anymore in IE8.

After searching on Google I found an solution for this problem ( here ) and I also found another possible solution ( here tanks to Mark Hildreth’s ). The problem is caused by IE8 because it does not handle the z-index off the page right. You can fix this problem by doing the following:

Create a new class in your CSS file or place it in your web page:

 .aspmenufix
 {
     z-index: 1;
 }

In the ASP menu control you have to add the following:

 <asp:Menu ID="Menu1" runat="server">
     <DynamicMenuStyle CssClass="aspmenufix" />
 </asp:Menu>

I hope this will help everyone who uses the ASP Menu in his website!

Scheduling SharePoint Backup : Part 2

A while ago I posted a SharePoint Backup script so that it is possible to schedule a SharePoint Backup. I had to make some adjustments to the script because it contains small errors.
The adjustments I had to make were:

  • When you perform multiple differential backups in a week you will receive multiple e-mails for one backup.
  • I have created more properties so that you can alter the script from the top.
  • You do not have to delete a method when you choose the differential backup method
  • I cleaned the script up a little bit.
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ''SharePoint Backup Script
 ''
 ''This is a VBScript for scheduling the SharePoint backup.
 ''The script has the ability to be scheduled as an differential
 ''backup or a full backup. This script will also send an email
 ''when the job is finished.
 ''
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ''declaration off constants that are needed to perform the backup
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 'email address from
 Const strFrom = ""

 'email address to
 Const strTo = ""

 'email subject timeout
 Const strSubjectTimeOut = "Sharepoint Backup Process - Time Out"

 'message for the time out
 Const strTimeOutMessage = "The SharePoint backup process has been running for over 60 minutes. Please check the progress of the backup. To make sure everything is going as it should be going."

 'email subject failed
 Const strSubjectFailes = "Sharepoint Backup Process - Failed"

 'email message failed
 Const strFailedMessage = "The SharePoint Backup Process failed with errors. Please check the errors and run the backup process again."

 'email subject success
 Const strSubjectSucceeded = "Sharepoint Backup Process - Succeeded"

 'email message success
 Const strSucceededMessage = "The SharePoint Backup Process completed without errors."

 'backup enviroment
 Const strEnviroment = "Intranet"

 'url off the mail server
 Const strMailserver = ""

 'backup location, this has to be a shared drive
 Const strBackUpDir = ""

 'backupmethod full or differential
 Const strBackUpMethod = "differential"

 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 ''Beginning the backup process
 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

 'object for checking succeeded update
 bool = "false"

 'create windows shell object
 Set objShell = CreateObject("WScript.Shell")

 if strBackUpMethod = "full" Then
     Call ClearFolder(strBackUpDir)
 End If

 'retrieve the 12hive from sharepoint from the registery
 strMossPath = objShell.RegRead ("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\12.0\Location") & "BIN\"

 'Define the path off the sharepoint location. This will point to the file with specific information about the backup.
 SharPointBackupRestoreTable = strBackUpDir & "\spbrtoc.xml"

 'Ensure that we run in the sharepoint folder
 objShell.CurrentDirectory = strMossPath

 'Execute the stsadm backup command
 objShell.Exec ("stsadm -o backup -directory " & strBackUpDir & " -backupmethod " & strBackUpMethod & " ")

 ' This Do loop checks the status of the backup process every minute.
 ' If the backup process hasn't completed within 60 minutes an email is sent to the
 ' Sharepoint administrator notifying him/her about this, otherwise an email is sent
 ' notifying the SharePoint Administrator of the outcome of the backup
 Do

 loopCounter = loopCounter + 1

 If count > 60 Then
 'Send the e-mail that it took 60 minutes
 Call SendEmail(strSubject, strTimeOutMessage, strEnviroment)
 End If
 ' Wait for 1 minute
 WScript.Sleep 60000

 ' Check if the backup process (i.e. stsadm.exe) is currently running
 strComputer = "."
 Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
 Set sharepointProcess = objWMIService.ExecQuery ("Select * from Win32_Process Where Name = 'stsadm.exe'")

 If (sharepointProcess.count) = 0 Then
 'Backup process has ended therefore check the SharePoint Backup Restore Table to analyse the outcome of the backup
 Set objXMLDoc = CreateObject("Microsoft.XMLDOM")
 objXMLDoc.async = False
 objXMLDoc.load(SharPointBackupRestoreTable)
 Set NodeList = objXMLDoc.documentElement.selectNodes("//SPErrorCount")
 'check each node
 For Each Node In NodeList
 If (Node.text) <> "0" Then
 bool = "false"
 Else
 bool = "true"
 End If
 Next

 'send message
 If bool="false" Then
 ' Backup errors were generated
 Call SendEmail(strSubjectFailed, strFailedMessage, strEnviroment)
 Else
 ' No backup errors were generated
 Call SendEmail(strSubjectSucceeded, strSucceededMessage, strEnviroment)
 End If
 Exit Do
 End If

 Loop

 ''Method for cleaning out the backup folder. It will delete all the old backups
 ''<strfolder>The String Off The Folder To Cleam</strfolder>
 Sub ClearFolder(strfolder)

 'Get file object
 Set fso=CreateObject("Scripting.FileSystemObject")
 CleanPath = fso.GetFolder(strfolder)

 'Delete all files
 For Each file In fso.GetFolder(CleanPath).Files
 file.delete
 Next

 'Loop trough the subfolders and put them in an array
 arrFolders = Array()
 For Each oFolder In fso.GetFolder(strfolder).SubFolders
 intCount = UBound(arrFolders) + 1
 ReDim Preserve arrFolders(intCount)
 arrFolders(intCount) = oFolder.Path
 Next

 'Loop trough the array and delete the subfolders
 For n = 0 To UBound(arrFolders)
 fso.DeleteFolder arrFolders(n), True
 Next

 End Sub

 ''Mehtod for sending the e-mail
 ''<subject>The subject off the email</subject>
 ''<body>The Body text off the e-mail</body>
 Sub SendEmail (subject, body, enviroment)

 'create message
 Set objEmail = CreateObject("CDO.Message")
 objEmail.From = strFrom
 objEmail.To = strTo
 objEmail.Subject = subject & " (" & strBackUpMethod & ") " & "- " & enviroment
 objEmail.Textbody = body

 'Mail configuration
 objEmail.Configuration.Fields.Item _
 ("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
 objEmail.Configuration.Fields.Item _
 ("http://schemas.microsoft.com/cdo/configuration/smtpserver") = strMailserver
 objEmail.Configuration.Fields.Item _
 ("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
 objEmail.Configuration.Fields.Update

 'Send E-mail
 objEmail.Send
 'Write to the event log
 Call WriteEvent(subject,body)
 End Sub

 ''Method for Logging the outcome off the backup to the application event log
 ''<subject>The subject off the event</subject>
 ''<body>The Body text off the event</body>
 Sub WriteEvent(subject,body)
 If subject = "SharePoint Backup Process Completed Successfully" Then
 objShell.LogEvent 0, body
 Else
 objShell.LogEvent 1, body
 end If
 End Sub

If you want to change the settings off this VBScript you only have to change the values at the top off the file:

  • strFrom: The e-mail address off the person where you would like to send the e-mail.
  • strTo: The e-mail address of the person where the e-mail comes from.
  • strMailserver:The mail server for sending the e-mail.
  • strBackUpDir:Backup location.
  • BackUpMethod:The Backup method you can choose full or differential.
  • strEnviroment:The environment where you run the backup script. If you use multiple environments and send it to one address you can see about witch environment the message is about.
  • strSucceededMessage:The message when the backup is succeeded.
  • strSubjectTimeOut:The subject for the timeout email.
  • strTimeOutMessage:The message for the timeout email.
  • strSubjectFailes:The subject for the failed email.
  • strFailedMessage:The message for the failed email.
  • strSubjectSucceeded:The subject for the message when the backup succeeded

If you want to schedule the execution off the VBScript you can read my previous post : here.

If you want to download the file you can click here:
SharePoint Backup

Content Editor WebPart ControlAdapter

One of our clients had a internet facing website. With this website we had a number of problems because it was deployed to our Internet facing farm with Content Deployment. The problem we had was that the Content Editor Web Part (for the rest of this article I will name it CEWP) does not allow relative URLs but saves the complete URL.

So the Internet site had many URLs in the content editor part the referenced to the internal address that you could not access from the internet. Till a few weeks ago we did not have a solution to this problem till i read a blog post (here and here). They created a Control Adapter that is run when the content editor web part is on a certain page. I have chosen to write out my complete solution because I found that the other post could have been written with more details on how to create it and how to implement it.

The Control Adapter replaces the URLs of the internal site in the CEWP with "/"  so that it displays a relative URL so that you can use the link on the internet. So what do you need for this solution:

  • A feature for adding a link to Central Administration and also places a browser file in the App_Browser Directory for a certain web application.
  • A application page for saving the URL that has to be saved in the web application properties.
  • A browser file to set the Control Adapter.

First lets make the browser file to set the Control Adapter to the CEWP:

<browsers>
    <browser refID="Default">
        <controlAdapters>
            <adapter controlType="Microsoft.SharePoint.WebPartPages.ContentEditorWebPart" adapterType="CEWPFIX.SharePoint.CEWPFIXAdapter, CEWPFIX.SharePoint, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1e8dcda25bf26f8e" />
        </controlAdapters>
    </browser>
</browsers>

What you do in the browser file is that you assign a Adapter to the Microsoft.SharePoint.WebPartPages.ContentEditorWebPart and that is the CEWPFIXAdapter in the example above.

The next step is to make the Control Adapter. So we created a class file and inherit from the System.Web.UI.Adapters.

using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.Adapters;
using System.IO;
using System.Text.RegularExpressions;
using Microsoft.SharePoint.Administration;
using System.Web.Caching;
using Microsoft.SharePoint;
namespace CEWPFIX.SharePoint
{
    /// <summary>
    /// ControlAdapter for the Content Editor WebPart
    /// </summary>
 ControlAdapter
    {
        /// <summary>
        /// Constant string for the creation of the cache object
        /// </summary>
        private const string AlternateUrls = "AlternateUrls";
        /// <summary>
        /// Key for the webapplication property
        /// </summary>
        private const string FilterUrl = "FilterUrls";
        /// <summary>
        /// Override the render function off the CEWP
        /// </summary>
        /// <param name="writer">HtmlTextWriter write</param>
        protected override void Render(HtmlTextWriter writer)
        {
            //Create a new stringbuilder
            StringBuilder sb = new StringBuilder();
            //create a HtmlTextWriter
            HtmlTextWriter htw = new HtmlTextWriter(new StringWriter(sb));
            //Preform the base Render Method
            base.Render(htw);
            //Get the output string
            string output = sb.ToString();
            //Create a list with the alternate access urls
            List<Uri> alternateUrls = GetAlternateUrls();
            //Read out the list with alternate access urls
            foreach (Uri alternateUrl in alternateUrls)
            {
                //Replace the url's with /
                output = output.Replace(alternateUrl.ToString(), "/");
            }
            //write the output to the page
            writer.Write(output);
        }
        /// <summary>
        /// Method for retrieving the Alternate Access Urls of retrieving them from the Cache
        /// </summary>
        /// <returns></returns>
        private List<Uri> GetAlternateUrls()
        {
            //Try to retrieve the list from the cache
            List<Uri> alternateUrls = (List<Uri>)HttpContext.Current.Cache[AlternateUrls];
            //Make sure anonymous users can access the web application properties
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                //If the list is null recreate the list en put it in the chache
                if (alternateUrls == null)
                {
                    //Create a new list object
                    alternateUrls = new List<Uri>();
                    //Retrieve the WebApplication object
                    SPWebApplication webApp = SPWebApplication.Lookup(HttpContext.Current.Request.Url);
                    if (webApp.Properties.ContainsKey(FilterUrl))
                    {
                        string[] urls = webApp.Properties[FilterUrl].ToString().Split(new char[] { ';' });
                        foreach (string str in urls)
                        {
                            try
                            {
                                Uri url = new Uri(str);
                                alternateUrls.Add(url);
                            }
                            catch (Exception ex)
                            {
                                new ExceptionHandler("Something went wrong while creating a uri in the CEWPFIXAdapter", ex);
                            }
                        }
                    }
                    //Add the list to the cache
                    HttpContext.Current.Cache.Add(AlternateUrls, alternateUrls, null, DateTime.Now.AddHours(12), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
                }
            });
            //return the list with the urls
            return alternateUrls;
        }
    }
}

This code will make sure the URLs will get the right format.

Disposing of SharePoint Objects (SPDisposeCheck tool Finally Available!)

On the internet you can find several excellent articles on the various design patterns for disposing of SharePoint or WSS objects.
Some of these objects can cause serious memory leaks if you don’t use them correctly. Normally you have to take care of three different objects: SPSite, SPWeb and Publishing.

There are a couple of White Papers available on MSDN that you can read about this subject:

Best Practices: Using Disposable Windows SharePoint Services Objects
Best Practices: Common Coding Issues When Using the SharePoint Object Model.

Yesterday a tool was released for checking if you dispose of the objects in a correct way. The tool is called SPDisposeCheck. SPDisposeCheck is a tool to help you check your assemblies that use the SharePoint API so that you can build better code. The tool may not show all memory leaks and further investigation is advised if you continue to experience issues.

spdisposecheck

You can download SPDisposeCheck here.

I checked the tool out by applying it to one of our project and we found 21 problems. We fixed those problems and we certainly saw an improvement. So try this tool out and let it help you with building better code.