The Publishing Cache of SP2010 WebApplications

October 11, 2010

In a recent post I talked about the publishing cache. It turns out this also applies for Classic Authentication WebApplications.

(from the original post:)
Have a look into your Developer Dashboard to see what is going on:

The warning and critical error are:

  • 7362 – Warning Publishing Cache
  • 7363 – Critical Publishing Cache

I have created a script to set the cacheSuperAccount and cacheReaderAccount on each webapplication in the farm. Be sure to create two seperate domain user accounts first. Then use this script from within you SP2010 PowerShell Prompt. It works on both Classic and Claims Based Authentication WebApps.
Usage:

.\script.ps1 -cacheSuperAccount "domain\superuser" -cacheReaderAccount "domain\superreader"

The updated PowerShell Script:

param([string]$cacheSuperAccount= "sp2010\superuser",[string]$cacheReaderAccount= "sp2010\superreader")

write-host ""
write-host -f White "Configure the WebApp property: portalsuperuseraccount and portalsuperreaderaccount"

write-host ""
write-host -f Green "Stef van Hooijdonk - v1.0"
# http://stefvanhooijdonk.com/2010/07/06/claims-based-authentication-and-the-publishingcache/
write-host ""

$snapin="Microsoft.SharePoint.PowerShell"
if (get-pssnapin $snapin -ea "silentlycontinue") {
	write-host -f Green "PSsnapin $snapin is loaded"
}
else {
	if (get-pssnapin $snapin -registered -ea "silentlycontinue") {
		write-host -f Green "PSsnapin $snapin is registered"
		Add-PSSnapin $snapin
		write-host -f Green "PSsnapin $snapin is loaded"
	}
	else {
		write-host -f Red "PSSnapin $snapin not found"
	}
}

[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")

write-host -f Green "Getting current Farm"
$farm = Get-SPFarm

$cacheSuperAccountCL = New-SPClaimsPrincipal -Identity $cacheSuperAccount -IdentityType WindowsSamAccountName
$cacheReaderAccountCL= New-SPClaimsPrincipal -Identity $cacheReaderAccount -IdentityType WindowsSamAccountName
$cacheSuperAccountCL = $cacheSuperAccountCL.ToEncodedString()
$cacheReaderAccountCL= $cacheReaderAccountCL.ToEncodedString()

write-host ""
write-host -f Green "Going to loop WebApplications"

Get-SPWebApplication | foreach-object {

	write-host ""

	if ($_.UseClaimsAuthentication -eq $true ) {

		write-host -f white $_.Url " is a Claims Based Authentication WebApp"

		write-host -f yellow " - Setting Policy: $cacheSuperAccountCL to Full Control for WebApp" $_.Url
		$policy1 = $_.Policies.Add($cacheSuperAccountCL ,$cacheSuperAccount )
		$policy1.PolicyRoleBindings.Add( $_.PolicyRoles.GetSpecialRole( [Microsoft.SharePoint.Administration.SPPolicyRoleType]::FullControl) )

		write-host -f yellow " - Setting Property: portalsuperuseraccount $cacheSuperAccountCL for" $_.Url
		$_.Properties["portalsuperuseraccount"] = $cacheSuperAccountCL

		write-host -f yellow " - Setting Policy: $cacheReaderAccountCL to Full Read for WebApp" $_.Url
		$policy2 = $_.Policies.Add($cacheReaderAccountCL ,$cacheReaderAccount )
		$policy2.PolicyRoleBindings.Add( $_.PolicyRoles.GetSpecialRole( [Microsoft.SharePoint.Administration.SPPolicyRoleType]::FullRead) )


		write-host -f yellow " - Setting Property: portalsuperreaderaccount $cacheReaderAccountCL for" $_.Url
		$_.Properties["portalsuperreaderaccount"] = $cacheReaderAccountCL
	}
	else  {
		write-host -f white $_.Url " is a Classic Authentication WebApp"

		write-host -f yellow " - Setting Policy: $cacheSuperAccount to Full Control for WebApp" $_.Url
		$policy1 = $_.Policies.Add($cacheSuperAccount ,$cacheSuperAccount )
		$policy1.PolicyRoleBindings.Add( $_.PolicyRoles.GetSpecialRole( [Microsoft.SharePoint.Administration.SPPolicyRoleType]::FullControl) )

		write-host -f yellow " - Setting Property: portalsuperuseraccount $cacheSuperAccount for" $_.Url
		$_.Properties["portalsuperuseraccount"] = "$cacheSuperAccount"

		write-host -f yellow " - Setting Policy: $cacheReaderAccount to Full Read for WebApp" $_.Url
		$policy2 = $_.Policies.Add($cacheReaderAccount ,$cacheReaderAccount )
		$policy2.PolicyRoleBindings.Add( $_.PolicyRoles.GetSpecialRole( [Microsoft.SharePoint.Administration.SPPolicyRoleType]::FullRead) )


		write-host -f yellow " - Setting Property: portalsuperreaderaccount $cacheReaderAccount for" $_.Url
		$_.Properties["portalsuperreaderaccount"] = "$cacheReaderAccount"
	}

	$_.Update()
	write-host "Saved properties"
}

Write ""
Write-host -f red "Going to run IISReset"
IISreset /noforce
Write ""
Advertisements

SharePoint 2010 August Cumulative Update

September 2, 2010

Microsoft just released the SharePoint Foundation Cumulative Update for August 2010.

SharePoint Foundation 2010:
kb2266423Download

SharePoint Server 2010:
kb2352342Download

(Updated to point the urls to the correct hotfixes)

via:
http://blogs.msdn.com/b/aaronsaikovski
http://blogs.technet.com/b/stefan_gossner

Federated Search and Twitter

August 31, 2010

So was trying some federated search the other day, and ended up with a Twitter Federated Search Location.

Download the zipped Twitter.OSDX and add it to your Federated Search Locations and enjoy searching in twitter! ( I presume you know how to add a Federated Search Results Webpart to your Search Center ).

Writing your own Trusted Identity provider for SP2010 (2)

August 31, 2010

This is part two of a Multi Blog post on “writing your own Trusted Identity provider / Claim Provider for SP2010“.
In the first post I covered:

In this post I will cover:

Create a Custom SPClaimProvider

For SharePoint 2010 to Trust any Identity Provider, we need a SPClaimProvider specific for that provider. This SPClaimProvider has two main purposes:

  • Provide a way for SharePoint to communicate with any Trusted Identity Provider in a uniform (Interface) way.
  • And provide SharePoint a way to use the same Claims for Users that have logged in through a different (e.g. AD) Identity Provider, let’s call this Claims Augmentation

To create a SPClaimProvider follow the following steps:

  • Create a new VS2010 Empty SP2010 Project
  • And add references to Microsoft.IdentityModel, Microsoft.SharePoint and Microsoft.SharePoint.Security.
  • Create a new Class, let’s name it CustomClaimsProvider and inherit from:Microsoft.SharePoint.Administration.Claims.SPClaimProvider
  • Implement all the methods for resolving claims, this is so we can use the claims provided within SharePoint to give rights to claims:
    • FillHierarchy
    • FillResolve
    • FillSearch, FillSchema
    • FillClaimTypes,FillClaimValueTypes,FillEntityTypes
  • Implement GetClaimsForEntity for the claims augmentation
  • Override the SupportsEntityInformation,SupportsHierarchy,,SupportsResolve and SupportsSearch properties and let them “return True” since we have implemented all the Fill Methods for this SPClaimProvider
  • Give your Provider a Name by overriding the Property Name. You will need this later on.

In fairness, this post has an excellent description on the subject also.
Sample implementation of FillClaimsForEntity

/// <summary>
/// Get's the username part of a claims authentication login claim
/// figure out who the user is so we know what team to add to their claim
/// the entity.Value from the input parameter contains the name of the
/// authenticated user.  for a SQL FBA user, it looks something like
/// 0#.f|sqlmembership|user1; for a Windows claims user it looks something
/// 0#.w|steve\\wilmaf
/// 0#.t|Customsleden|stef@tamtam.nl
/// </summary>
/// <param name="identityClaims"></param>
/// <returns>only the login part</returns>
public static string GetLoginPartFromIdentityClaim(string identityClaims) {
    string login = identityClaims;
    if (!string.IsNullOrEmpty(login) && login.Contains("|")) {
        string[] parts = login.Split('|');
        if (parts.GetLength(0) == 3) // formsbased or trusted provider based
            login = parts[2];
        if (parts.GetLength(0) == 2) // windows based
            login = parts[1];
    }
    return login;
}

/// <summary>
/// Add Claims to a logged in User, from Dynamics CRM info
/// </summary>
/// <param name="context">Current Context, url</param>
/// <param name="entity">the logged in user/claim</param>
/// <param name="claims">The list of claims for the user</param>
protected override void FillClaimsForEntity(Uri context, SPClaim entity, List<SPClaim> claims) {
    if (claims == null)
        throw new ArgumentNullException("claims");
    if (entity == null)
        throw new ArgumentNullException("entity");

    Logging.LogMessage(entity.Value);

    string login = Utilities.GetLoginPartFromIdentityClaim(entity.Value);

    // if windows user, get the email ?
    if (!string.IsNullOrEmpty(login)) {
        Logging.LogMessage(string.Format("User is {0}", login));

        CRMClient.Entities.User user = null;
        string email = login;
        if (login.Contains("@")) {
            user = CRMClient.CrmSQLClient.GetUserByEmail(login);
        }
        if (login.Contains("\\")) {
            user = CRMClient.CrmSQLClient.GetUser(login);
            if (user != null)
                email = user.Email;
        }
        // employee, crm users
        if (user != null) {
            try {
                Logging.LogMessage(string.Format("User is SystemUser of CRM with an license, Name:{0}", user.FullName));

                claims.Add( GetClaim(CustomClaimsProvider.CustomAccountTypeClaimType, "everyone"));

                SPClaim employee = null;
                if (user.FullLicense) { // admin and or full license
                    employee = GetClaim(CustomClaimsProvider.CustomAccountTypeClaimType, "employee");
                }
                else {// readonly
                    employee = GetClaim(CustomClaimsProvider.CustomAccountTypeClaimType, "employeero");
                }
                if (employee != null)
                    claims.Add(employee);
            }
            catch (Exception exc) {
                Logging.LogException(exc);
            }
        }
        else {
            Logging.LogMessage(string.Format("{0} not found as SYSTEMUSER in CRM", login));
        }
    }
}

Register your Claims Provider for SharePoint

In order for your Claims Provider to be registered within SharePoint 2010 you will need to create a specific type of Feature.

  • Add a Farm Feature to your Project
  • Add an EventReicever to your new Feature
  • And let your receiver inherit from SPClaimProviderFeatureReceiver (see: Register)
  • And now implement the following Properties in your Feature Event Receiver
    • ClaimProviderDisplayName
    • ClaimProviderDescription
    • ClaimProviderAssembly
    • ClaimProviderType

You can use this to return the last two properties:

/// <summary>Get the Full Assembly Name of the Claim provider we want to register</summary>
public override string ClaimProviderAssembly {
    get {
        return typeof(CustomClaimsProvider).Assembly.FullName;
    }
}

/// <summary>Get the Class Type of the Claim provider we want to register</summary>
public override string ClaimProviderType {
    get {
        return typeof(CustomClaimsProvider).FullName;
    }
}

That’s it, you can now register your Claims Provider, and if you want you can use this as is.
Next post will be on these subjects:

  • Create a Trust between your Tusted Identity Provider (STS) and SharePoint 2010
  • Create or Configure your SP2010 WebApplication to use the Tusted Identity Provider

PDF Icon in SharePoint

August 27, 2010

This one has been long overdue. There are ton’s of posts and manuals on how to add the PDF Icon to SharePoint. But I wanted an even simpler solution. Litterly one solution!
So here it is!
I have created a simple Farm solution. With just one Farm Feature.
When activated it will add the PDF icon to the infamous DOCICON.xml and upload the PDF Icon itself off course.

And then you will have the PDF Icon for your PDF documents.

Download the solution here.
If you want to index the contents of a PDF file with SharePoint search, don’t forget to install the Adobe PDF iFilter or the Foxit PDF iFilter ( my favorite ).
Install the solution ( from within the SP2010 Powershell window ) and as Farm Admin!

write-host -f Green "Add solution: TamTam.SP2010.PDFIcon.wsp"
Get-ChildItem TamTam.SP2010.PDFIcon.wsp | Add-SPSolution
write-host -f Green "Install solution: TamTam.SP2010.PDFIcon"
Install-SPSolution -Identity "TamTam.SP2010.PDFIcon.wsp" -GacDeployment

Here is the code from the solution to add the PDF Icon xml element to the DOCICON.xml and to all the Search Applications of the Farm. I also added the removal code in the FeatureDeactivating code in the solution itself!

public class PDFIconEventReceiver : SPFeatureReceiver {

    /// <summary>Const of the docicon FileName</summary>
    private const string dociconFile = "TEMPLATE\\XML\\DOCICON.xml";

    /// <summary>
    /// Handle the activation of the feature for the PDF Icon for SP2010,
    /// Adds an entry to the SharePoint DOCICON.xml file for the PDF Icon in this solution
    /// </summary>
    /// <param name="properties"></param>
    public override void FeatureActivated(SPFeatureReceiverProperties properties) {

        string dociconXmlFile = string.Format("{0}\\..\\..\\..\\{1}",
            properties.Feature.Definition.RootDirectory,
            dociconFile);

        XmlDocument docicon = new XmlDocument();
        docicon.Load(dociconXmlFile);

        // locate existing PDF icon
        XmlNode pdfnode = docicon.SelectSingleNode("//DocIcons/ByExtension/Mapping[@Key='pdf']");
        if (pdfnode == null) {
            // no existing PDF icon found, add to the file:
            XmlNode rootextentionNode = docicon.SelectSingleNode("//DocIcons/ByExtension");
            if (rootextentionNode != null) {
                // add new Mapping
                pdfnode = docicon.CreateElement("Mapping");

                XmlAttribute key = docicon.CreateAttribute("Key");
                // extention for this mapping
                key.Value = "pdf";
                pdfnode.Attributes.Append(key);

                XmlAttribute value = docicon.CreateAttribute("Value");
                // url of this solution's pdf icon image
                value.Value = "TamTam.SP2010.PDFIcon/pdficon_small.gif";
                pdfnode.Attributes.Append(value);

                rootextentionNode.AppendChild(pdfnode);
                // save new docicon file to disk
                docicon.Save(dociconXmlFile);
            }
        }
        // add the PDF Icon to the SearchApplications
        foreach (SearchServiceApplication service in SearchService.Service.SearchApplications) {
            // open the Content
            Content content = new Content(service);
            // register the icon as an Extention
            try { content.ExtensionList.Create("pdf"); }
            catch { }
        }
    }
}

SharePoint and Web.config modifications

August 25, 2010

Ever since MOSS 2007 we all know the Web.Config Modifications Manager within the SharePoint API. But today I stumbled upon this PowerShell commandlet: Add-WebConfiguration. This commandlet is from the IIS7(.5) powershell module WebAdministration.

The short version is, you can use this in you deploy script to do Web.Config modifications

Example for an AppSettings Configuration Key:”

Add-WebConfiguration /appSettings "IIS:\sites\Default Web Site" -atIndex 0 -Value @{key="DBConnectionString";value="server=10.2.34.1"}

This will result in the following addition of your Web.Config.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appSettings>
        <add key="DBConnectionString" value="server=10.2.34.1" />
    </appSettings>
</configuration>

Note: Be carefull with ANY Web.Config modification, you can break stuff!
Load the WebAdministration module in powershell:

#load webadministration powershell module
$hasSnapin = get-pssnapin | Select { $_.Name.toLower().Trim() = "webadministration" }
if ($hasSnapin -ne $null) {
  # IIS7 / Windows 2008
  add-pssnapin WebAdministration
} else {
  # IIS7.5 / Windows 2008 R2
  import-module WebAdministration
}

Provisioning Publishing Pages

August 23, 2010

On the interweb there seems to be some confusion on how to provision publishing resources through features.
Here is what works for SharePoint 2010.
First you wil need a Feature scoped for Site (SPSite), put your ContenType module here.
And a Feature scoped for Web and add the other modules here!

1. Create a Page ContentType

Add a New ContentType to your project. VS 2010 will ask you from which ContentType you want to inherit, select Article Page.
You will get a new ContentTypeId!
Supply a Name, Group and Description for your Page ContentType.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <!-- Parent ContentType: Article Page (0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D) -->
  <ContentType ID="0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D00d77cd9769a6443cd8dce93ee832e8303"
               Name="CustomPageContentType"
               Group="Site Content Types"
               Description="My Page Content Type"
               Inherits="TRUE"
               Version="0">
    <FieldRefs>
      <!-- Supply fields here -->
    </FieldRefs>
  </ContentType>
</Elements>

Add the (Publishing) Fields you want to use in your Publishing Pages.

2. Provision a MasterPage

Now add an empty Module, name it MasterPages and remove the sample.txt.
Add your Site.Master page to your newly created module
Open the Element.xml of your module
Apply the following changes:

  • Add RootWebOnly=”TRUE” to you Module
  • Add a Title to your File
  • Add a Description to your File
  • Add a ContentType to your File with the value $Resources:cmscore,contenttype_masterpage_name;, cause this is a masterpage!
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <!-- Mind the RootWebOnly attibute here -->
  <Module Name="MasterPages" Url="_catalogs/masterpage" RootWebOnly="TRUE">
    <File Path="PageLayouts\Site.master" Url="Ordina.master" Type="GhostableInLibrary" >
      <!-- Required -->
      <Property Name="Title" Value="Site Master Page" />
      <!-- optional -->
      <Property Name="MasterPageDescription" Value="Master Page for Site" />
      <!-- optional -->
      <Property Name="PublishingPreviewImage" Value="" />
      <!-- Required and important, leave this value like this (MasterPage) -->
      <Property Name="ContentType" Value="$Resources:cmscore,contenttype_masterpage_name;" />
    </File>
  </Module>
</Elements>

3. Provision a Publishing PageLayout

Add your PageLayout to either a new Module or the MasterPage Module. If you create a new Module, don’t forget to add the RootWebOnly=”TRUE”.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <!-- Mind the RootWebOnly attibute here -->
  <Module Name="PageLayouts" Url="_catalogs/masterpage" RootWebOnly="TRUE">
    <File Path="PageLayouts\PageLayout.aspx" Url="PageLayouts/PageLayout.aspx" Type="GhostableInLibrary" >
      <!-- Required -->
      <Property Name="Title" Value="Site WelcomePage" />
      <!-- Optinal -->
      <Property Name="MasterPageDescription" Value="Description" />
      <!-- Required and important, leave this value like this, THIS IS A PageLayout! -->
      <Property Name="ContentType" Value="$Resources:cmscore,contenttype_pagelayout_name;" />
      <!-- Required and important and use your CustomPageContentType ContentTypeId here ! -->
      <Property Name="PublishingAssociatedContentType"
                Value=";#CustomPageContentType;#0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D00d77cd9769a6443cd8dce93ee832e8303;#"/>
      <!-- Optional, add default webpart(s) to zones -->
      <AllUsersWebPart WebPartZoneID="WebPartZoneTop" WebPartOrder="1">
        <![CDATA[ WebPart XML here ]]>
      </AllUsersWebPart>
    </File>
  </Module>
</Elements>

You can also add “default” WebParts to you page layout with the AllUsersWebPart tag.

4. Provision a Publishing Page

And you can even provision Publishing Pages with a module.
Now add an empty Module, name it DefaultPages and remove the sample.txt.
Open the Element.xml of your module
I have provided a sample here, please note the following:

  • Use the ContentTypeBinding to bind your CustomPageContentType to the Pages list
  • Use the default.aspx from the default SharePoint Site Definition SPS by adding the SetupPath attribute to your Module (more here).
  • You can provision WebParts and Field Values
  • To deploy multiple Publishing Pages add another File ellement, and change the Url attribute of the File Element, to create another page.
  • You can use the feature to provision Pages from within you Onet.xml, but don’t forget to activate dependand features first ( Publishing Infrastructure, Publishing, optional ContentTypes )
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <!-- Add your CustomPageContentType ContentTypeId to the Pages List, have had problems with this element in subsites. -->
  <ContentTypeBinding ContentTypeId="0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D00d77cd9769a6443cd8dce93ee832e8303"
                      ListUrl="Pages" />
  <Module Name="ProvisionDefaultPages" Url="$Resources:osrvcore,List_Pages_UrlName;"
          SetupPath="SiteTemplates\SPS" >
    <!-- Note the Url of this File element -->
    <File Path="default.aspx" Url="default.aspx" Type="GhostableInLibrary">
      <!-- Required -->
      <Property Name="Title" Value="HomePage" />
      <!-- Required, Use the ContentType CustomPageContentType you created earlier,no need for the ContentType ID, Name is enough ! -->
      <Property Name="ContentType" Value="CustomPageContentType" />
      <!-- Required, Use the PageLayout you created earlier -->
      <Property Name="PublishingPageLayout" Value="~SiteCollection/_catalogs/masterpage/PageLayout.aspx, Site WelcomePage" />
      <!-- Optional Extra attributes -->
      <Property Name="PublishingPageContent" Value="WebContent field value" />
      <!-- Optional, add default webpart(s) to zones -->
      <AllUsersWebPart WebPartZoneID="WebPartZoneTop" WebPartOrder="1">
        <![CDATA[ WebPart XML here ]]>
      </AllUsersWebPart>
    </File>
    <!-- Note the Url of this File element -->
    <File Path="default.aspx" Url="anotherpage.aspx" Type="GhostableInLibrary">
      <!-- Required -->
      <Property Name="Title" Value="Another Page" />
      <!-- Required, Use the CustomPageContentType ContentType you created earlier, no need for the ContentType ID, Name is enough -->
      <Property Name="ContentType" Value="CustomPageContentType" />
      <!-- Required, Use the PageLayout you created earlier -->
      <Property Name="PublishingPageLayout" Value="~SiteCollection/_catalogs/masterpage/PageLayout.aspx, Site WelcomePage" />
      <!-- Optional Extra attributes -->
      <Property Name="PublishingPageContent" Value="WebContent field value" />
      <!-- Optional, add default webpart(s) to zones -->
      <AllUsersWebPart WebPartZoneID="WebPartZoneTop" WebPartOrder="1">
        <![CDATA[ WebPart XML here ]]>
      </AllUsersWebPart>    </File>
  </Module>
</Elements>

5. Small bonus tip

When you deploy MasterPages and PageLayouts through features, they become part of the site collection. You provision these files in the _catalog/MasterPages SPList of your Site Collection! Now default SharePoint won’t load the contents of your deployed masterpages and pagelayouts into the actual ContentDb. It only uses a reference in the ContentDb to the actual file on Disk in your Feature folder. So updating these PageLayouts and MasterPages can be done by uploading a new Solution with the updated Files.
But user’s can change files in the Site Collection through SharePoint Designer. If a User Customizes a MasterPage or PageLayout, it becomes Customized or Un-Ghosted. This means the file contents are now stored in the ContentDb! And when you re-deploy your solution with your new feature and files (update the feature version number!) you would expect to see your changes, but you won’t!

You can use the object model to undo this, Re-Ghosting, by calling the RevertContentStream() on the SPFile of your PageLayout or MasterPage.
I have written a method for that which you can call from the Feature Event Receiver Activate and Upgrade (version number of your feature 😉 ):

    /// <summary>
    /// This class handles events raised during feature activation, deactivation, installation, uninstallation, and upgrade.
    /// </summary>
    /// <remarks>
    /// The GUID attached to this class may be used during packaging and should not be modified.
    /// </remarks>
    [Guid("fa5fdea4-f3be-4b17-8942-528517b2e28c")]
    public class ProvisioningEventReceiver : SPFeatureReceiver {

        public override void FeatureActivated(SPFeatureReceiverProperties properties) {
            UpdateFilesForFeature(properties);
        }

        //public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        //{
        //}

        public override void FeatureUpgrading(SPFeatureReceiverProperties properties, string upgradeActionName, System.Collections.Generic.IDictionary<string, string> parameters) {
            UpdateFilesForFeature(properties);
        }

        /// <summary>
        /// Update files for a Feature, based on defaults and file extentions.
        /// </summary>
        /// <param name="feature">The feature</param>
        public static void UpdateFilesForFeature(SPFeatureReceiverProperties feature) {
            // locate the webpart and dwp file for this feature
            string layoutdirectory = feature.Definition.RootDirectory;
            if (!layoutdirectory.EndsWith("/"))
                layoutdirectory += "/";
            layoutdirectory += "PageLayouts";

            string[] featurefiles = System.IO.Directory.GetFiles(layoutdirectory);

            List<string> featurefilesList = new List<string>();
            foreach (string filename in featurefiles) {
                string ext = System.IO.Path.GetExtension(filename).ToLowerInvariant();

                if (ext.EndsWith("aspx") || ext.EndsWith("master") || ext.EndsWith("xml"))
                    featurefilesList.Add(System.IO.Path.GetFileName(filename).ToLowerInvariant());

                Logging.LogMessage("added to list: " + filename);
            }

            if (featurefilesList.Count > 0) {
                Logging.LogMessage("no files found");
                SPSite site = Base.Feature.GetActivationSite(feature);
                if (site != null) {
                    // locate the masterpages/page layouts list
                    SPList masterpagegallery = null;

                    foreach (SPList list in site.RootWeb.Lists) {
                        if (list.BaseTemplate == SPListTemplateType.MasterPageCatalog) {
                            masterpagegallery = list;
                            break;
                        }
                    }
                    if (masterpagegallery != null) {
                        Logging.LogMessage("Checking Masterpage Gallery: " + masterpagegallery.Title);
                        Logging.LogMessage("items " + masterpagegallery.Items.Count);
                        foreach (SPListItem pagelayout in masterpagegallery.Items) {
                            Logging.LogMessage("checking layout " + pagelayout.Name);
                            if (featurefilesList.Contains(pagelayout.File.Name.ToLowerInvariant())) {
                                // just uncustomize the pages for the given feature, so that MOSS will point to
                                // the disk version of these (new) files.
                                if (pagelayout.File.CustomizedPageStatus == SPCustomizedPageStatus.Customized) {
                                    // make the page uncustomized
                                    pagelayout.File.RevertContentStream();
                                    Logging.LogMessage("Uncustomized the item: " + pagelayout.File.Name);
                                }
                                else {
                                    Logging.LogMessage("The item was already uncustomized: " + pagelayout.File.Name);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

Writing your own Trusted Identity provider for SP2010 (1)

August 19, 2010

With the introduction of the Windows Identity Framework and SharePoint 2010 it is now possible to have multiple authentication providers on a Single URL on a SPWebApplication.
Especialy in an Extranet scenario this is very usefull:

  • Employees login with their AD Account
  • Partners, suppliers, customers login through a (Custom) Trusted Identity Provider ( Like facebook,OpenID or custom )

Image you have a CRM Application (like Microsoft Dynamics CRM 4) where you store all the relations (contacts) your company has with customers, partners and suppliers. You can use this information, e.g. the email addresses and a custom field for a password, to create your own Identity Provider and provide access to your extranet/collaboration portal based on the information stored in the CRM application.

In 2007 you would extend your SPWebApplication and configure Forms Based Authentication. You would lose the Client Integration Features of Office (2007), and you needed to configure your Membership Provider in the Web.Config.

In 2010 we can use a Identity provider/Claim Provider. There are some major differences / benefits in comparison to a custom Membership Provider. With an Identity Provider ( custom or not ):

  • You can use the Integration Features of the Office (2010) Client
  • All users connect on the same url / WebApplication
  • You can re-use a Identity Provider on more than one WebApp (it’s now just a Trust thing)
  • You have Single Sing On (SSO) for free with other WebApps that Trust the same Identity Provider
  • Configuration is done through PowerShell on all servers at once!

This will be a Multi Blog post on “writing your own Trusted Identity provider / Claim Provider for SP2010”.
In order to have a working Trusted Identity provider for SharePoint you will need to do a couple of things:

This post will focus on how to create a Custom Security Token Service with the Windows Identity Framework SDK.

  1. Download and install Windows Identity Framework SDK for .Net 4.0 ( and thus VS 2010 )
    SP2010 runs on .Net 3.5 but since this is a Separate IIS WebApplication you use the 4.0
  2. Create a new Web Project with VS2010 based on the ASP.Net Security Token Service Web Site template

    This will be the Website used by users to actually login to

  3. Note the following important classes/methods in the project:

    • CustomSecurityTokenService.cs, and especially the method: GetOutputClaimsIdentity
    • Login.aspx, and especially the CodeBehind: Page_Load
    • Web.Config, and especially the appSettings: IssuerName and SigningCertificateName
  4. Implement the method GetOutputClaimsIdentity,
    here I have chosen to login with the user’s email address.
    /// <summary>This method returns the claims to be issued in the token.</summary>
    /// <param name="principal">The caller's principal.</param>
    /// <param name="request">The incoming RST, can be
    /// used to obtain addtional information.</param>
    /// <param name="scope">The scope information
    /// corresponding to this request.</param>
    /// <exception cref="ArgumentNullException">If 'principal'
    /// parameter is null.</exception>
    /// <returns>The outgoing claimsIdentity to be included
    /// in the issued token.</returns>
    protected override IClaimsIdentity GetOutputClaimsIdentity(IClaimsPrincipal principal,
                                            RequestSecurityToken request,
                                            Scope scope) {
        if (null == principal) {
            throw new ArgumentNullException("principal");
        }
        // name = the email address of the user
        string name = principal.Identity.Name;
        // create the new identity
        ClaimsIdentity outputIdentity = new ClaimsIdentity();
        // add new claim based on login/email
        outputIdentity.Claims.Add(new Claim(
          System.IdentityModel.Claims.ClaimTypes.Email,
          principal.Identity.Name));
        // return the claim
        return outputIdentity;
    }
    
    If you want to add more claims you can! You could add claims to the output here to facilitate groups of users.
  5. Implement the Login button/event in the Login page
    /// <summary>Handle the Page Load</summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void Page_Load(object sender, EventArgs e) {
        // Note: Add code to validate user name, password. This code is for illustrative purpose only.
        // Do not use it in production environment.
        if (!string.IsNullOrEmpty(txtUserName.Text)) {
            // Check credentials agains backoffice (Dynamics CRM 4 in this case)
            if (CrmSQLClient.ValidateContactLogin(txtUserName.Text, txtPassword.Text)){
                // Login info is valid, redirect the user.
                if (Request.QueryString["ReturnUrl"] != null) {
                    FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, false);
                }
                else {
                    FormsAuthentication.SetAuthCookie(txtUserName.Text, false);
                    Response.Redirect("default.aspx");
                }
            }
        }
    }
    
  6. Change the login page front end
  7. Add extra functions like “Forgot my password” or “Register for an account” now or later
  8. Deploy your webapplication to IIS.
    You should now be able to login to your custom STS with a browser:
  9. Create a proper “FederationMetadata.xml”.
    This was a bit hard for me at first. But there is an App for that!

    • Provide the proper values for the WS-Federation Metadata Generator
    • You will need a Certificate to sign the “FederationMetadata.xml” document
    • Copy the new “FederationMetadata.xml” to your published STS Website
  10. Test your new STS WebApplication by logging in. You might get an error when you first test this. Use this url to test if your login page works:http://test-server/default.aspx?&wa=wsignin1.0
  11. There is one thing left to do when you want to use your Identity Provider: install a certificate on the Web Server, and use that to sign the FederationMetadata.xml and update the Web.Config of your provider to reference this certificate in the IssuerName.

Next post will descibe the SPClaimProvider to create and register.

And be sure to check-out some small “issues” with Claims Based authentication

SPModule, install SharePoint 2010 through PowerShell

August 17, 2010

Dan Winter & Zach Rosenfield inform us that they have released SPModule:

“SPModule is an example of how we would envision you accomplish various common tasks within Windows PowerShell in a SharePoint 2010 environment.  We hope to position various best practices from these scripts and we hope in the long term to reference these also within technet.  These blog posts serve simply as our first location of sharing them, and this post will be updated once we have the samples hosted within technet.  The scripts themselves are not officially supported.”

The post

The download

Example: “Install SharePoint Bits (including Prereqs)”

Install-SharePoint -SetupExePath “\\servername\SharePoint2010\setup.exe” -PIDKey "PKXTJ-DCM9D-6MM3V-G86P8-MJ8CY"
New-SharePointFarm –DatabaseAccessAccount Get-Credential DOMAIN\username –DatabaseServer "SQL01" –FarmName "TestFarm"

SharePoint 2010 Site recycle bin released

August 4, 2010

The SP2010 team released the Admin tool we have all be waiting for: The SharePoint Site Recycle Bin for SharePoint 2010 is available for download at http://governance.codeplex.com

“The SharePoint Site Recycle Bin is a SharePoint Foundation 2010 solution package that when deployed to a Microsoft SharePoint Foundation 2010 or Microsoft SharePoint Server 2010 server farm enables administrators to create a snapshot of subscriptions, site collections and Webs as they are deleted through the SharePoint user interface, the SharePoint Administration Tool, the SharePoint 2010 Management Shell, SharePoint 2010 Central Administration, or SharePoint Designer.”

Via http://blogs.technet.com/b/wbaer/archive/2010/08/04/sharepoint-site-recycle-bin-for-sharepoint-2010.aspx