Auto Publish and Approve Your Solution Files

Applies To: SharePoint 2010

By default, every file you deploy using a sandboxed solution is left checked out. This can lead to problems depending on the type of site you are deploying to and/or the permissions of your end users.

This post focuses on Branding solutions, but anytime you are deploying a sandboxed solution these techniques should help you. This is especially important for Master Pages since these often need an approved/published version in order to be visible to anyone but the site administrators.

I found an interesting approach by Waldek Mastykarz where he suggests using a “Stamp” (Feature ID property) on each file and using that to find and check in each file. This was very cool, but requires you to modify the Elements.xml entry for each file to ensure that property is added and he also never addressed Master Pages which can be a bit of a special case.

For my needs, I generally take a simpler approach of just deploying my resources to one root folder and creating sub folders as needed. This makes it easier to find stuff, but it also means I don’t need to track each file individually. Obviously if you are doing something a little more extensive then you may need to take a hybrid approach of using the featureid property and/or just tracking the various folders you are deploying too. But for a simple branding solution you really just need to:

  1. Apply your branding to each site
  2. Publish and Approve each resource file
  3. Publish and Approve each Master Page

All of this can be done in the FeatureActivating event with a simpler helper method:

    Public Overrides Sub FeatureActivated(ByVal properties As SPFeatureReceiverProperties)
        Dim siteCollection As SPSite = CType(properties.Feature.Parent, SPSite)
        If siteCollection IsNot Nothing Then
            Dim topSite As SPWeb = siteCollection.RootWeb

            'Calculate relative path to site from Web Application root
            Dim WebAppRelativePath As String = topSite.ServerRelativeUrl
            If Not WebAppRelativePath.EndsWith("/") Then WebAppRelativePath &= "/"

            'Enumerate through each site and apply branding
            For Each site As SPWeb In siteCollection.AllWebs
                If Not site.MasterUrl.EndsWith("minimal.master") Then
                    site.MasterUrl = WebAppRelativePath & "_catalogs/masterpage/BSmain.master"
                Else
                    site.MasterUrl = WebAppRelativePath & "_catalogs/masterpage/BSminimal.master"
                End If
                If Not site.CustomMasterUrl.EndsWith("minimal.master") Then
                    site.CustomMasterUrl = WebAppRelativePath & "_catalogs/masterpage/BSmain.master"
                Else
                    site.CustomMasterUrl = WebAppRelativePath & "_catalogs/masterpage/BSminimal.master"
                End If
                site.AlternateCssUrl = WebAppRelativePath & "Style%20Library/BSResources/BS.css"
                site.SiteLogoUrl = WebAppRelativePath & "Style%20Library/BSResources/Images/BSlogo.png"
                site.UIVersion = 4
                site.Update()
            Next

            'Publish and Approve each file
            Dim styleLibrary As SPList = topSite.Lists.TryGetList("Style Library")
            If styleLibrary IsNot Nothing Then
                Dim folders As SPListItemCollection = styleLibrary.Folders
                Dim item As SPListItem = DirectCast((From i In folders Where DirectCast(i, SPListItem).Url = "Style Library/BSResources" Select i).FirstOrDefault(), SPListItem)
                ApproveAndPublish(item.Folder, styleLibrary.EnableModeration)
            End If

            'Publish and Approve the Master Pages
            Dim mpGallery As SPList = siteCollection.GetCatalog(SPListTemplateType.MasterPageCatalog)
            If mpGallery IsNot Nothing Then
                Dim mpages As SPListItemCollection = mpGallery.GetItems(New SPQuery With {.Query = "<Where><Or><Eq><FieldRef Name='FileLeafRef' /><Value Type='Text'>RegalIC.master</Value></Eq><Eq><FieldRef Name='FileLeafRef' /><Value Type='Text'>RegalICminimal.master</Value></Eq></Or></Where>"})
                If mpages IsNot Nothing Then
                    For Each i As SPListItem In mpages
                        If Not i.File.CheckOutType = SPFile.SPCheckOutType.None Then
                            i.File.CheckIn("Feature Activation", SPCheckinType.MajorCheckIn)
                            If mpGallery.EnableModeration Then
                                i.File.Approve("Feature Activation")
                            End If
                        End If
                    Next
                End If
            End If

        End If
    End Sub

    Private Sub ApproveAndPublish(folder As SPFolder, Approve As Boolean)
        If folder Is Nothing Then Return
        For Each subfolder As SPFolder In folder.SubFolders
            ApproveAndPublish(subfolder, Approve)
        Next
        For Each file As SPFile In folder.Files
            If Not file.CheckOutType = SPFile.SPCheckOutType.None Then
                file.CheckIn("Feature Activation", SPCheckinType.MajorCheckIn)
                If Approve Then
                    file.Approve("Feature Activation")
                End If
            End If
        Next
    End Sub

1. Apply your branding to each site

After gathering basic information about where the feature is being deployed and getting the correct reference URLs, we begin looping through every site in the sitecollection and setting the master page, sitelogo, and CSS settings to use our custom branding beginning in line 11.

The only thing different than the approach described in the Microsoft article, Deploying Branding Solutions for SharePoint 2010 Sites Using Sandboxed Solutions is that I am checking if the current master page is the minimal.master and if so, using my BSminimal.master file instead. This allows me to have both master pages deployed correctly, but it also allows me to restore these settings more accurately in the deactivating event (See my previous post).

2. Publish and Approve each resource file

To keep things simple, I keep all of my resource files in a single root folder within the Style Library. This makes looping through each subfolder and resource very simple to ensure that each one gets checked in and/or approved as necessary.

Lines 29-34 get a reference to the resource folder within the Style Library and pass that information over to a helper method called ApproveAndPublish. This method takes an SPFolder reference and a boolean indicating if approval is necessary or not. For the initial call, the folder is our resource folder and the approval setting comes directly from the Style Library and is found in the EnableModeration property of the SPList object.

The ApproveAndPublish method (Lines 55-68) is a recursive function that loops through every subfolder and checks in every file found. If Approval is required, it also marks them as approved.

This means you don’t have to track each file (either through stamping or keeping a list). This really cuts down on all the plumbing that is often necessary when working on a SharePoint solution.

3. Publish and Approve each Master Page

Unfortunately, Master Pages aren’t usually deployed to a sub folder and so the above technique for approval and check in has to be tweaked slightly. Lines 37-50 take care of this. Basically, we get a reference to the Master Page Catalog and use some basic CAML to isolate our master pages and then loop through them to check them in and/or activate them if required.

That’s all that’s required. You now have your files successfully deployed and ready to be used. Be sure to check out my previous post Branding Solution Cleanup. In that post I describe how to remove all of your solution files when your solution gets deactivated.

Branding Solution Cleanup

Applies To: SharePoint 2010

I followed the Microsoft article, Deploying Branding Solutions for SharePoint 2010 Sites Using Sandboxed Solutions and I was able to quickly get the bones of a Branding project put together. Unfortunately, I found that when the solution was deactivated all the files I deployed remained exactly where they were.

I found various solutions for removing your files ranging from individual file lists to marking every file with your feature ID, but for a simple Branding project all you really need to do is:

  1. Remove usage of your Master Pages from every site referencing them
  2. Remove your files from the Style Library
  3. Remove your Master Page files from the Master Page Catalog

The first two can be done in the FeatureDeactivating event handler and the third can be done in the FeatureUninstalling event handler. For those that just want the code, here it is:

    Public Overrides Sub FeatureDeactivating(ByVal properties As SPFeatureReceiverProperties)
        Dim siteCollection As SPSite = CType(properties.Feature.Parent, SPSite)
        If siteCollection IsNot Nothing Then
            Dim topSite As SPWeb = siteCollection.RootWeb

            'Calculate relative path to site from Web Application root
            Dim WebAppRelativePath As String = topSite.ServerRelativeUrl
            If Not WebAppRelativePath.EndsWith("/") Then WebAppRelativePath &= "/"

            'Enumerate through each site and remove branding
            For Each site As SPWeb In siteCollection.AllWebs
                If Not site.MasterUrl.EndsWith("minimal.master") Then
                    site.MasterUrl = WebAppRelativePath & "_catalogs/masterpage/v4.master"
                Else
                    site.MasterUrl = WebAppRelativePath & "_catalogs/masterpage/minimal.master"
                End If
                If Not site.CustomMasterUrl.EndsWith("minimal.master") Then
                    site.CustomMasterUrl = WebAppRelativePath & "_catalogs/masterpage/v4.master"
                Else
                    site.CustomMasterUrl = WebAppRelativePath & "_catalogs/masterpage/minimal.master"
                End If
                site.AlternateCssUrl = String.Empty
                site.SiteLogoUrl = String.Empty
                site.Update()
            Next

            'Kill Style Library Folder
            Dim styleLibrary As SPList = topSite.Lists.TryGetList("Style Library")
            If styleLibrary IsNot Nothing Then
                Dim folders As SPListItemCollection = styleLibrary.Folders
                Dim item As SPListItem = DirectCast((From i In folders Where DirectCast(i, SPListItem).Url = "Style Library/BSResources" Select i).FirstOrDefault(), SPListItem)
                item.Delete()
            End If

        End If
    End Sub

    Public Overrides Sub FeatureUninstalling(ByVal properties As SPFeatureReceiverProperties)
        Dim siteCollection As SPSite = properties.UserCodeSite
        If siteCollection IsNot Nothing Then

            'Kill Master Pages
            Dim mpGallery As SPList = siteCollection.GetCatalog(SPListTemplateType.MasterPageCatalog)
            If mpGallery IsNot Nothing Then
                Dim mpages As SPListItemCollection = mpGallery.GetItems(New SPQuery With {.Query = "<Where><Or><Eq><FieldRef Name='FileLeafRef' /><Value Type='Text'>BSmain.master</Value></Eq><Eq><FieldRef Name='FileLeafRef' /><Value Type='Text'>BSminimal.master</Value></Eq></Or></Where>"})
                If mpages IsNot Nothing Then
                    For i As Integer = mpages.Count - 1 To 0 Step -1
                        mpages(i).Delete()
                    Next
                End If
            End If

        End If
    End Sub

Wow code! Alright Scriptkitties, copy away! Everyone else, here’s what we’re doing and why:

1. Remove usage of your Master Pages from every site referencing them

After gathering basic information about where the feature was deployed and figuring out the correct reference URLs, we begin looping through every site in the sitecollection and resetting the master page to the defaults beginning in line 11.

We are just undoing what was done in the FeatureActivating event. The only thing of note is that I hate when a Branding solution replaces every MasterPage with theirs and then just blindly restores v4.master. Mostly this is fine, but if one of your subsites is an Enterprise Search site or anything else using the minimal.master you’ve just wrecked it. Obviously if you know you aren’t using minimal.master then you can simplify this section. Also, I always name my minimal.master replacement in the form [Something]minimal.master to ensure this works out.

2. Remove your files from the Style Library

To keep things simple, I keep all of my resource files in a single root folder within the Style Library. Obviously I have subfolders to organize images, fonts, etc. but all of those are within my one folder. This makes finding stuff much easier, but more than that it makes removing the files super easy – Just delete that folder.

Lines 27-33 do just that. After getting a reference to the Style Library (Every sitecollection in SharePoint 2010 has one of these), grab the folder (just replace the “Style Library/BSResources” string in line 31 with your folder path) and delete.

3. Remove your Master Page files from the Master Page Catalog

There are lots of guides for deleting deployed master pages and I didn’t find any that worked. Basically every time I tried to delete a master page from within the FeatureDeactivating event I got an error about them still being used. I’m sure there’s a good reason for this (feel free to let me know in the comments), but it doesn’t really matter because as long as you followed step 1 above, it’ll work in the FeatureUninstalling event.

We simply grab a reference to the sitecollection’s Master Page Gallery and use some simple CAML to grab references to our Master Pages. Then we walk through them and delete them.

That’s it, you now have a self-cleaning solution and you are a responsible member of the SharePoint community.