Aidan Garnish

Collaboration Not Competition

Unable to delete Master Pages or Page Layouts?

I have just been tidying up my masterpage library in a WCM sitecollection and came across an issue when trying to delete some master pages.

When trying to delete I got the following error:

I know that this is not the case - no other pages use this master page!

Fortunatley there is a workaround for this. Simply create another folder in the masterpage folder and then drag the master page you want to delete into it. Then delete the folder and your master page is gone.

Thanks to Katrien De Graeve for this solution.

Solution to the ampersand (&) gotcha in SiteMapPath breadcrumb

If you name a site or page with an & and you want to also use the breadcrumb SiteMapPath control then you are going to see some nasty results. This is because the control outputs "&" instead of "&".

To fix this you need to make a change to the sitemappath control. Simply include SiteMapProvider="CurrentNavSiteMapProviderNoEncode" as follows:

 <asp:SiteMapPath runat="server" id="SiteMapPath1" PathSeparator="|" SiteMapProvider="CurrentNavSiteMapProviderNoEncode"></asp:SiteMapPath>

MOSS 2007 - CSS Crackers!

There is a  nasty gotcha in MOSS SharePoint CSS registration. It doesn't matter what order you register your css files, they will be rendered in the html in alphabetical order! Not very helpful...

Overriding core.css in MOSS 2007

So you are trying to use a custom css in your master page but the nice shiny font you are trying to display isn't showing up! The reason for this is that core.css will be called after your custom css file leaving your text displaying as verdana or arial.

The fix for this is to declare your custom css file in the master page settings of your site - (Site Actions/Site Settings/Master Page). The result is that your custom css will be applied after core.css and the font you specified is used.

For more interesting scenarios in which a bit of tweaking is required to get the right css file to be applied see http://www.heathersolomon.com/blog/archive/2006/10/27/sp07cssoptions.aspx

ILMerge to the rescue

I recently needed to add a reference to a web part where the dll had not been strongly named. The result of doing this was a build error complaining about the unsigned dll. I did not have access to the source code so couldn't resolve the issue by rebuilding the dll with a strong name. Instead I used the ILMerge tool which can be found here.

Rather than using the tool for it's intended use of to combining a number of dlls into a single dll I used it to sign my single dll using the following command line syntax:

ILMerge.exe /out:c:\signed.dll pathToFile\unsigned.dll /keyfile:pathToFile\aidangarnish.snk

MOSS 2007 double hop woes - and solution!

I have recently been working on a web part that accesses a SQL Server that is installed on a different server to the MOSS web front end. This web part worked great running under my own credentials which also had access to the SQL Server....until I tried running the page from a web browser on my own machine. At that point I get a login failed error as my credentials did not get as far as the SQL Server.

This was due to the double hop issue in Windows networks that only allows an application to pass the credentials it is running across one server hop. This meant that whilst running the web part from the MOSS Server I could hop once to the SQL Server and get the data. When running the web part from my PC I hopped once to the MOSS Server and from there was unable to pass my credentials across the next hop to the SQL Server.

After doing some investigation I realised that I could get around this using Kerberos and configuring AD correctly but as I don't have access to AD this is something I wanted to avoid.

Instead I came to the realisation that I didn't really need to pass the logged in user's credentials to the SQL Server as the information being pulled out is available to all users. At first I thought the solution was to use impersonation by adding a service account to the Web.Config of my SharePoint web application which is then used to access SQL.

This line needs to be added to Web.Config to do this:

<identity impersonate="true" userName="domain\serviceAccountUser" password="*******"/>

This user is then given permissions on SQL.......but that didn't solve the problem entirely!

Although my web part could now access SQL the whole of the SharePoint web application was running using the service account. The downside of this is that the user is no longer logged in as themselves so all their security permissions to document libraries, lists etc. are no longer applied.

Instead I tried using impersonation just around the piece of code that accesses the database using the following article - http://support.microsoft.com/kb/306158

I modified this code slightly to create a SecurityHelper class which I then used in my web part to impersonate the service account that had access to the database.

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Security.Principal;
namespace ProductSearch
{
    class SecurityHelpers
    {
        public SecurityHelpers() { }

        public const int LOGON32_LOGON_INTERACTIVE = 2;
        public const int LOGON32_PROVIDER_DEFAULT = 0;

        WindowsImpersonationContext impersonationContext;

        [DllImport("advapi32.dll")]
        public static extern int LogonUserA(String lpszUserName,
            String lpszDomain,
            String lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            ref IntPtr phToken);
        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int DuplicateToken(IntPtr hToken,
            int impersonationLevel,
            ref IntPtr hNewToken);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool RevertToSelf();

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern bool CloseHandle(IntPtr handle);

        public bool impersonateValidUser(String userName, String domain, String password)
        {
            WindowsIdentity tempWindowsIdentity;
            IntPtr token = IntPtr.Zero;
            IntPtr tokenDuplicate = IntPtr.Zero;

            if (RevertToSelf())
            {
                if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE,
                    LOGON32_PROVIDER_DEFAULT, ref token) != 0)
                {
                    if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                    {
                        tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                        impersonationContext = tempWindowsIdentity.Impersonate();
                        if (impersonationContext != null)
                        {
                            CloseHandle(token);
                            CloseHandle(tokenDuplicate);
                            return true;
                        }
                    }
                }
            }
            if (token != IntPtr.Zero)
                CloseHandle(token);
            if (tokenDuplicate != IntPtr.Zero)
                CloseHandle(tokenDuplicate);
            return false;
        }

        public void undoImpersonation()
        {
            impersonationContext.Undo();
        }
    }
}

The following is the code from my web part that makes use of the SecurityHelper class:

SecurityHelpers oHelper = new SecurityHelpers();
            if (oHelper.impersonateValidUser(strSQLUser, strSQLUserDomain, strSQLPass))
            {
                ddlBrand.AutoPostBack = false;
                ddlBrand.Visible = true;

                try
                {

                    SqlDataSource sqlDS = new SqlDataSource();
                    sqlDS.ConnectionString = ConfigurationManager.ConnectionStrings["MyCnString"].ConnectionString;

                    sqlDS.SelectCommand = "spGetBrands";
                    sqlDS.SelectCommandType = SqlDataSourceCommandType.StoredProcedure;
                    sqlDS.SelectParameters.Add("Market", "P");
                    sqlDS.Select(DataSourceSelectArguments.Empty);

                    ddlBrand.DataSource = sqlDS;
                    ddlBrand.DataTextField = "Brand";
                    ddlBrand.DataBind();
                    ddlBrand.Items.Insert(0, "All");

                }
                catch (Exception err)
                {
                    // string strErr = err.Message;
                }
                finally
                {
                    oHelper.undoImpersonation();

                }
            }
            else
            {
                //Your impersonation failed. Therefore, include a fail-safe mechanism here.
            }