UPDATE 8/20/2011: I’ve reworked this script and included it as part of my SharePoint 2010 cmdlet downloads. See “Resetting SharePoint 2010 Themes – Part 2, the Reset-SPTheme cmdlet” for details.

One of my current clients is a local school district here in Denver and we (Aptillon) have recently helped them release a new public facing site for the main district office as well as all the schools in the district. The district has chosen a somewhat fixed brand which has had full theming support added so that the individual schools can adjust the color scheme to match the school’s colors. The master page, page layouts, CSS files, and associated images were all deployed to the Farm using various Solution Packages (WSPs). This allows us to make corrections/additions to the brand related files and quickly deploy them to the Farm, thereby updating the district and school sites quite easily. The problem, however, is that the way theming works within SharePoint 2010 is that it makes a copy of all the CSS and image files and stores them in the /_catalogs/theme/Themed/{THEMEID} folder, as shown in the following figure:

Whenever you apply a theme it will copy all the necessary CSS and image files to a new folder in the Themed folder. This means that the site is no longer basing its look and feel off of the Feature deployed files. So if a school has gone in and customized their site to use themes then any updates that we push out to the style sheets and images will be ignored. So I needed a way to effectively “reset” the applied theme after we push out an update to our branding solution. By reset I mean preserve the various color and font settings but re-apply them against the Feature deployed files.

I did some digging with my favorite tool, Reflector, and found that the out of the box theme settings page just uses the Microsoft.SharePoint.Utilities.ThmxTheme class to manipulate the themes. So, after a little experimenting I ended up with some code which will regenerate the current theme using all the source files and the user provided theme settings:

 1#NOTE: Run in a seperate console instance each time otherwise the changes won't get applied
 2function Reset-SPTheme([Microsoft.SharePoint.PowerShell.SPSitePipeBind]$spSite) {
 3    $site = $spSite.Read()
 4    try {
 5        # Store some variables for later use
 6        $tempFolderName = "TEMP"
 7        $themedFolderName = "$($site.ServerRelativeUrl)/_catalogs/theme/Themed"
 8        $themesUrlForWeb = [Microsoft.SharePoint.Utilities.ThmxTheme]::GetThemeUrlForWeb($site.RootWeb)
 9        Write-Host "Old Theme URL: $themesUrlForWeb"
10        
11        # Open the existing theme
12        $webThmxTheme = [Microsoft.SharePoint.Utilities.ThmxTheme]::Open($site, $themesUrlForWeb)
13        
14        # Generate a new theme using the settings defined for the existing theme
15        # (this will generate a temporary theme folder that we'll need to delete)
16        $webThmxTheme.GenerateThemedStyles($true, $site.RootWeb, $tempFolderName)
17        
18        # Apply the newly generated theme to the root web
19        [Microsoft.SharePoint.Utilities.ThmxTheme]::SetThemeUrlForWeb($site.RootWeb, "$themedFolderName/$tempFolderName/theme.thmx", $true)
20        
21        # Delete the TEMP folder created earlier
22        $site.RootWeb.GetFolder("$themedFolderName/$tempFolderName").Delete()
23        
24        # Reset the theme URL just in case it has changed (sometimes it will)
25        $site.Dispose()
26        $site = $spSite.Read()
27        $themesUrlForWeb = [Microsoft.SharePoint.Utilities.ThmxTheme]::GetThemeUrlForWeb($site.RootWeb)
28        Write-Host "New Theme URL: $themesUrlForWeb"
29
30        # Set all child webs to inherit.
31        if ([Microsoft.SharePoint.Publishing.PublishingWeb]::IsPublishingWeb($site.RootWeb)) {
32            $pubWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($site.RootWeb)
33            $pubWeb.ThemedCssFolderUrl.SetValue($pubWeb.Web.ThemedCssFolderUrl, $true)
34        } else {
35            foreach ($web in $site.AllWebs) {
36                if ($web.isRootWeb) { continue }
37                Write-Host "Setting theme for $($web.ServerRelativeUrl)..."
38                try {
39                    [Microsoft.SharePoint.Utilities.ThmxTheme]::SetThemeUrlForWeb($web, $themesUrlForWeb)
40                } finally {
41                    $web.Dispose()
42                }
43            }   
44        }
45    } finally {
46        if ($site -ne $null) { $site.Dispose() }
47    }
48}

I saved this to a file named Reset-SPTheme so I can then call the function like so:

1. c:\Scripts\Reset-SPTheme.ps1
2Reset-SPTheme "http://example.com/"

One odd thing I found, however, is that every time I run this it must be run in a new console instance; otherwise the changes are not picked up (this is basically a combination of a threading and caching issue within the code when executed from a PowerShell context). So remember, start a new PowerShell console every time you need to run this script (yeah, I wasted a couple of hours banging my head against the wall trying to figure that little nugget out).