Resetting SharePoint 2010 Themes – Part 2, the Reset-SPTheme cmdlet
Yesterday I threw up a quick post showing how to reset a SharePoint 2010 theme using a reasonably simple Windows PowerShell script. In that post I promised that I’d convert the script to a cmdlet and make it part of my downloadable extensions. Well, as promised I’ve updated my extensions so that they now include a Reset-SPTheme cmdlet. I added on minor enhancement over the previously shown script in that I allow you to pass in either an SPSite or an SPWeb object and by default it will not force all child webs to inherit from the relevant SPWeb object. This way, if you have a child Site with it’s own theme it won’t wipe out that theme. If you have multiple Sites with a custom theme setting within a Site Collection then you’ll want to provide the -Site parameter and pass in an SPSite reference – this will result in all Sites with custom themes within the Site Collection to be reset. If you only wish to reset a single Site then use the -Web parameter and pass in a SPWeb reference.
Here’s the full help for the Reset-SPTheme cmdlet:
NAME
Reset-SPTheme
SYNOPSIS
Resets a theme by applying all user specified theme configuration settings to the original source files. This is particularly helpful when the original source files have changed to a Feature upgrade.
SYNTAX
Reset-SPTheme [-Web] <SPWebPipeBind> [-SetSubWebsToInherit <SwitchParameter>] [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]
Reset-SPTheme [-Site] <SPSitePipeBind> [-SetSubWebsToInherit <SwitchParameter>] [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]
DESCRIPTION
Resets a theme by applying all user specified theme configuration settings to the original source files. This is particularly helpful when the original source files have changed to a Feature upgrade.
Copyright 2011 Falchion Consulting, LLC
> For more information on this cmdlet and others:
> http://blog.falchionconsulting.com/
> Use of this cmdlet is at your own risk.
> Gary Lapointe assumes no liability.
PARAMETERS
-Web <SPWebPipeBind>
Specifies the URL or GUID of the Web containing the theme to reset.
The type must be a valid GUID, in the form 12345678-90ab-cdef-1234-567890bcdefgh; a valid name of Microsoft SharePoint Foundation 2010 Web site (for example, MySPSite1); or an instance of a valid SPWeb object.
Required? true
Position? 1
Default value
Accept pipeline input? true (ByValue, ByPropertyName)
Accept wildcard characters? false
-Site <SPSitePipeBind>
The site containing the theme to reset.
The type must be a valid GUID, in the form 12345678-90ab-cdef-1234-567890bcdefgh; a valid URL, in the form http://server_name; or an instance of a valid SPSite object.
Required? true
Position? 1
Default value
Accept pipeline input? true (ByValue, ByPropertyName)
Accept wildcard characters? false
-SetSubWebsToInherit [<SwitchParameter>]
If specified, all child webs will be reset to inherit the theme of the specified web or root web.
Required? false
Position? named
Default value
Accept pipeline input? false
Accept wildcard characters? false
-AssignmentCollection [<SPAssignmentCollection>]
Manages objects for the purpose of proper disposal. Use of objects, such as SPWeb or SPSite, can use large amounts of memory and use of these objects in Windows PowerShell scripts requires proper memory management. Using the SPAssignment object, you can assign objects to a variable and dispose of the objects after they are needed to free up memory. When SPWeb, SPSite, or SPSiteAdministration objects are used, the objects are automatically disposed of if an assignment collection or the Global parameter is not used.
When the Global parameter is used, all objects are contained in the global store. If objects are not immediately used, or disposed of by using the Stop-SPAssignment command, an out-of-memory scenario can occur.
Required? false
Position? named
Default value
Accept pipeline input? true (ByValue)
Accept wildcard characters? false
<CommonParameters>
This cmdlet supports the common parameters: Verbose, Debug,
ErrorAction, ErrorVariable, WarningAction, WarningVariable,
OutBuffer and OutVariable. For more information, type,
"get-help about_commonparameters".
INPUTS
OUTPUTS
NOTES
For more information, type "Get-Help Reset-SPTheme -detailed". For technical information, type "Get-Help Reset-SPTheme -full".
------------------EXAMPLE 1-----------------------
PS C:\> Get-SPSite http://server_name | Reset-SPTheme -SetSubWebsToInherit
This example resets the theme for the site collection http://server_name and resets all child webs to inherit from the root web.
------------------EXAMPLE 2-----------------------
PS C:\> Get-SPWeb http://server_name/sub-web | Reset-SPTheme
This example resets the theme for the web http://server_name/sub-web.
RELATED LINKS
Get-SPWeb
Get-SPSite
In the following example I’m resetting the theme(s) for an entire Site Collection. If one any child Sites within the Site Collection have a custom theme then they’ll be updated, not just the root (inheritance will not be changed):
PS C:\> Reset-SPTheme -Site http://example.com
In this next example I’m resetting all child Sites to inherit whatever theme has been specified for the root Site and I’m updating the root Site’s theme with changes to the source files:
PS C:\> Reset-SPTheme -Site http://example.com -SetSubWebsToInherit
For this last example I’m resetting the theme of a specific sub-Site:
PS C:\> Reset-SPTheme -Web http://example.com
As you can see, this is pretty easy to use and, if you’re deploying your branding via Features and you have theme support then a cmdlet like this can be quite critical when you need to push out updates to that brand.
Resetting SharePoint 2010 Themes
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:
#NOTE: Run in a seperate console instance each time otherwise the changes won't get applied function Reset-SPTheme([Microsoft.SharePoint.PowerShell.SPSitePipeBind]$spSite) { $site = $spSite.Read() try { # Store some variables for later use $tempFolderName = "TEMP" $themedFolderName = "$($site.ServerRelativeUrl)/_catalogs/theme/Themed" $themesUrlForWeb = [Microsoft.SharePoint.Utilities.ThmxTheme]::GetThemeUrlForWeb($site.RootWeb) Write-Host "Old Theme URL: $themesUrlForWeb" # Open the existing theme $webThmxTheme = [Microsoft.SharePoint.Utilities.ThmxTheme]::Open($site, $themesUrlForWeb) # Generate a new theme using the settings defined for the existing theme # (this will generate a temporary theme folder that we'll need to delete) $webThmxTheme.GenerateThemedStyles($true, $site.RootWeb, $tempFolderName) # Apply the newly generated theme to the root web [Microsoft.SharePoint.Utilities.ThmxTheme]::SetThemeUrlForWeb($site.RootWeb, "$themedFolderName/$tempFolderName/theme.thmx", $true) # Delete the TEMP folder created earlier $site.RootWeb.GetFolder("$themedFolderName/$tempFolderName").Delete() # Reset the theme URL just in case it has changed (sometimes it will) $site.Dispose() $site = $spSite.Read() $themesUrlForWeb = [Microsoft.SharePoint.Utilities.ThmxTheme]::GetThemeUrlForWeb($site.RootWeb) Write-Host "New Theme URL: $themesUrlForWeb" # Set all child webs to inherit. if ([Microsoft.SharePoint.Publishing.PublishingWeb]::IsPublishingWeb($site.RootWeb)) { $pubWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($site.RootWeb) $pubWeb.ThemedCssFolderUrl.SetValue($pubWeb.Web.ThemedCssFolderUrl, $true) } else { foreach ($web in $site.AllWebs) { if ($web.isRootWeb) { continue } Write-Host "Setting theme for $($web.ServerRelativeUrl)..." try { [Microsoft.SharePoint.Utilities.ThmxTheme]::SetThemeUrlForWeb($web, $themesUrlForWeb) } finally { $web.Dispose() } } } } finally { if ($site -ne $null) { $site.Dispose() } } }
I saved this to a file named Reset-SPTheme so I can then call the function like so:
. c:\Scripts\Reset-SPTheme.ps1 Reset-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).
BTW: I intend to add this to my cmdlet extensions as I believe it will be something I’ll need often so look for an updated build to come out this weekend.
SharePoint Server 2010 Service Pack 1 PowerShell Changes
As most people know by now, Service Pack 1 for SharePoint 2010 was released to the public today. There’s already been a lot of hype over some of the new capabilities such as the site recycle bin and some folks have documented/demonstrated some of the new PowerShell cmdlets that are available to manage this new feature; but what about all the other new and changed PowerShell cmdlets – there’s a bunch! So, let’s take a look at what is new, and what has changed.
We’ll start with the new stuff – here’s a quick bulleted list of all the new cmdlets:
- Add-SPProfileLeader
- Get-SPProfileLeader
- Remove-SPProfileLeader
- Remove-SPProfileSyncConnection
- Add-SPProfileSyncConnection
- Disable-SPHealthAnalysisRule
- Enable-SPHealthAnalysisRule
- Get-SPHealthAnalysisRule
- Get-SPDeletedSite
- Remove-SPDeletedSite
- Restore-SPDeletedSite
- Move-SPSocialComments
I haven’t had a chance to try any of these out but I think there’s some cool new functionality here beyond just the site recycle bin. There’s absolutely no documentation for any of these but some of them are fairly straightforward based on the names. For instance, the Add-SPProfileSyncConnection cmdlet (and equivalent Remove cmdlet) are obviously for managing the synchronization connections for UPS. This was a big whole in RTM when it came to doing an end-to-end scripted installation as there was no practical way to add a synchronization connection using PowerShell. The health analysis rule cmdlets are also pretty obvious and, again, this goes a long way towards enabling administrators to script a deployment and enable or disable rules consistently across Farms.
I think the Move-SPSocialComments also has a lot of potential; if it does what I’m guessing it does then this could potentially solve the issue where comments and tags are stored with the absolute URL of the item that has been tagged/commented on – I’m *guessing* that running this command will effectively retarget those items, which is great in situations in which you’ve moved a list or site.
As for the *-SPProfileLeader cmdlets – I have no idea what those guys do so I’ll have to revisit them when I learn more. (The *-SPDeletedSite cmdlets have already been covered quite a bit by others so I’ll forgo any further discussion here).
Alright, so that’s the new stuff – what about the stuff that’s changed? Here’s a quick list with the changes:
- Mount-SPContentDatabase
- New Switch Parameter: ChangeSyncKnowledge (I’ve no idea what this does – it’s not yet documented)
- New-SPContentDatabase
- New Switch Parameter: ChangeSyncKnowledge (again, not yet documented)
- Move-SPSite
- New Parameter: RbsProviderMapping <Hashtable>
- From TechNet: “Used to move an RBS-enabled site collection from one RBS-enabled content database to another RBS-enabled content database without moving the underlying BLOB content. If the content database has more than one RBS provider associated with it, you must specify all providers. The same providers must be enabled on the target content database and the source content database.”
- New Parameter: RbsProviderMapping <Hashtable>
- New-SPPerformancePointServiceApplication
- New Parameter: AnalyticResultCacheMinimumHitCount <Int32> (not yet documented but I think it’s fairly obvious what it does)
- New Parameters: DatabaseServer <string>, DatabaseName <string>, DatabaseFailoverServer <string>, DatabaseSQLAuthenticationCredential <PSCredential>
- Can I get a hurray for this! This was the only Service Application that didn’t allow us to set the database information when we created it so we were left with this nasty GUID in the name. Hurray! We can finally get rid of the last database GUID! Woohoo!
- Set-SPPerformancePointServiceApplication
- New Parameter: AnalyticResultCacheMinimumHitCount <Int32> (not yet documented but I think it’s fairly obvious what it does)
- New Parameters: DatabaseServer <string>, DatabaseName <string>, DatabaseFailoverServer <string>, DatabaseSQLAuthenticationCredential <PSCredential>, DatabaseUseWindowsAuthentication
- Remove-SPWeb
- New Switch Parameter: Recycle
- That’s right, you can in fact cause an SPWeb to go to the new recycle bin by simply providing this switch parameter.
- New Switch Parameter: Recycle
- Update-SPProfilePhotoStore (I think this one is a bit of mess and may need a CU or two but I could just be reading the code wrong – it is kind of late right now)
- Update 6/29/2011: From Spence Harbar: “Update-SPProfilePhotoStore change is to address common bug/issue with resize and is for upgrade scenarios”
- New Switch Parameter: CreateThumbnailsForImportedPhotos
- I’m not entirely sure what this is supposed to do as thumbnails were created previously and will be created without this; however, I should note that they coded this wrong so both of the following syntaxes have the same affect as they are simply checking that the bool? type has a value and not what the value is:
- -CreateThumbnailsForImportedPhotos $true
- -CreateThumbnailsForImportedPhotos $false
- I’m not entirely sure what this is supposed to do as thumbnails were created previously and will be created without this; however, I should note that they coded this wrong so both of the following syntaxes have the same affect as they are simply checking that the bool? type has a value and not what the value is:
- New Switch Parameter: NoDelete
- I’m not 100% on this but I believe that the original behavior of this cmdlet was to copy the image and not actually move it (despite the name); they appear to have changed the behavior to delete the original images after the copy but you can preserve the original behavior by simply adding this switch parameter. However, this is *only* true when the CreateThumbnailsForImportedPhotos parameter is provided (in my opinion this is a bug – it should be irrelevant if that parameter is provided).
Well, that’s all I’ve been able to discover with the core SharePoint Server 2010 cmdlets – I may update this post to account for the Office Web Applications but I don’t currently have that installed on the server in which I just installed SP1 so it may take me a bit to do that analysis. At some point I may write something up to inspect the public classes and their members and do a similar post for the developers out there who want to know what API changes have occurred so keep an eye out for that.
So I think it’s pretty cool that we’ve got some improvements in the PowerShell cmdlets, especially in some of those that have frustrated me when it comes to automated deployments; the real frustrating thing, however, is that some of my just released book content is already out of date! Ugh! Maybe there’ll be a second edition ![]()
Happy PowerShelling!
-Gary
Getting (and taking ownership of) Checked Out Files using Windows PowerShell
Often when I’m working on a project I need to generate a list of all checked out files and provide that to my client just prior to release to production. Sometimes the client will manually inspect each of them and act as they see fit and other times they’ll ask me to just batch publish all of them (for which I use my Publish-SPListItems cmdlet). So, how do I generate the report for the client? It’s actually pretty easy using PowerShell and a couple of quick loops. Here’s an example that loops through every Site Collection in the Farm and generates a nice report:
function Get-CheckedOutFiles() { foreach ($web in (Get-SPSite -Limit All | Get-SPWeb -Limit All)) { Write-Host "Processing Web: $($web.Url)..." foreach ($list in ($web.Lists | ? {$_ -is [Microsoft.SharePoint.SPDocumentLibrary]})) { Write-Host "`tProcessing List: $($list.RootFolder.ServerRelativeUrl)..." foreach ($item in $list.CheckedOutFiles) { if (!$item.Url.EndsWith(".aspx")) { continue } $hash = @{ "URL"=$web.Site.MakeFullUrl("$($web.ServerRelativeUrl.TrimEnd('/'))/$($item.Url)"); "CheckedOutBy"=$item.CheckedOutBy; "CheckedOutByEmail"=$item.CheckedOutByEmail } New-Object PSObject -Property $hash } foreach ($item in $list.Items) { if ($item.File.CheckOutStatus -ne "None") { if (($list.CheckedOutFiles | where {$_.ListItemId -eq $item.ID}) -ne $null) { continue } $hash = @{ "URL"=$web.Site.MakeFullUrl("$($web.ServerRelativeUrl.TrimEnd('/'))/$($item.Url)"); "CheckedOutBy"=$item.File.CheckedOutByUser; "CheckedOutByEmail"=$item.File.CheckedOutByUser.Email } New-Object PSObject -Property $hash } } } $web.Dispose() } } Get-CheckedOutFiles | Out-GridView
Running the above will generate a fairly nice report with URLs and usernames and whatnot; you could also use the Export-Csv cmdlet to dump the results to a CSV file that you can then hand off to your end-users. One cool thing to point out about this is that it will also show you files that you normally can’t see – that is files that have been created by other users but have never had a check in. This is actually pretty cool and I stumbled upon this when trying to fine tune my Publish-SPListItems cmdlet. You see, if the file has never been checked in then iterating through the SPListItemCollection object will not reveal the item (or file I should say); this meant that my cmdlet, as it was previously written, was missing a bunch of files. So to work around this all I had to do was add an additional loop to iterate over the collection returned by the SPDocumentLibrary’s CheckedOutFiles property. For each SPCheckedOutFile object in that collection I then call TakeOverCheckOut() to grab the checked out file so that I can then publish.
I use this enough that I decided to turn it into a cmdlet that is now part of my custom extensions. Like the above script, I return back a custom object that contains the full URLs and other useful information (such as the List, Site, and Site Collection identifiers). I also exposed a TakeOverCheckOut() and Delete() method which simply calls Microsoft’s implementation of those methods.
I called this cmdlet Get-SPCheckedOutFiles (note that I’d previously released this cmdlet under the name Get-SPFilesCheckedOut but have since reworked and renamed that original implementation).
Here’s the full help for the cmdlet:
PS C:\Users\spadmin> help Get-SPCheckedOutFiles -full
NAME
Get-SPCheckedOutFiles
SYNOPSIS
Retrieves check out details for a given List, Web, or Site.
SYNTAX
Get-SPCheckedOutFiles [-Site] <SPSitePipeBind> [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]
Get-SPCheckedOutFiles [-Web] <SPWebPipeBind> [-ExcludeChildWebs <SwitchParameter>] [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]
Get-SPCheckedOutFiles [[-Web] <SPWebPipeBind>] [-List] <SPListPipeBind> [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]
DESCRIPTION
Retrieves check out details for a given List, Web, or Site.
Copyright 2010 Falchion Consulting, LLC
> For more information on this cmdlet and others:
> http://blog.falchionconsulting.com/
> Use of this cmdlet is at your own risk.
> Gary Lapointe assumes no liability.
PARAMETERS
-Site <SPSitePipeBind>
Specifies the URL or GUID of the Site to inspect.
The type must be a valid GUID, in the form 12345678-90ab-cdef-1234-567890bcdefgh; a valid URL, in the form http://server_name; or an instance of a valid SPSite object.
Required? true
Position? 1
Default value
Accept pipeline input? true (ByValue, ByPropertyName)
Accept wildcard characters? false
-Web <SPWebPipeBind>
Specifies the URL or GUID of the Web to inspect.
The type must be a valid GUID, in the form 12345678-90ab-cdef-1234-567890bcdefgh; a valid URL, in the form http://server_name; or an instance of a valid SPWeb object.
Required? true
Position? 1
Default value
Accept pipeline input? true (ByValue, ByPropertyName)
Accept wildcard characters? false
-List <SPListPipeBind>
The list whose checked out files are to be returned.
The value must be a valid URL in the form http://server_name/lists/listname or /lists/listname. If a server relative URL is provided then the Web parameter must be provided.
Required? true
Position? 1
Default value
Accept pipeline input? true (ByValue, ByPropertyName)
Accept wildcard characters? false
-ExcludeChildWebs [<SwitchParameter>]
Excludes all child sites and only considers the specified site.
Required? false
Position? named
Default value
Accept pipeline input? false
Accept wildcard characters? false
-AssignmentCollection [<SPAssignmentCollection>]
Manages objects for the purpose of proper disposal. Use of objects, such as SPWeb or SPSite, can use large amounts of memory and use of these objects in Windows PowerShell scripts requires proper memory management. Using the SPAssignment object, you can assign objects to a variable and dispose of the objects after they are needed to free up memory. When SPWeb, SPSite, or SPSiteAdministration objects are used, the objects are automatically disposed of if an assignment collection or the Global parameter is not used.
When the Global parameter is used, all objects are contained in the global store. If objects are not immediately used, or disposed of by using the Stop-SPAssignment command, an out-of-memory scenario can occur.
Required? false
Position? named
Default value
Accept pipeline input? true (ByValue)
Accept wildcard characters? false
<CommonParameters>
This cmdlet supports the common parameters: Verbose, Debug, ErrorAction, ErrorVariable, WarningAction, WarningVariable, OutBuffer and OutVariable. For more information, type, "get-help about_commonparameters".
INPUTS
OUTPUTS
NOTES
For more information, type "Get-Help Get-SPCheckedOutFiles -detailed". For technical information, type "Get-Help Get-SPCheckedOutFiles -full".
------------------EXAMPLE------------------
PS C:\> Get-SPCheckedOutFiles -Site "http://server_name/"
This example outputs a list of files that are checked out for the given Site Collection
RELATED LINKS
Get-SPFile
In the following example I’m retrieving pages from the root Pages library that are checked out:

In this example I am running the cmdlet as the aptillon\spadmin user and I’m now able to see the checkout by the user aptillon\glapointe. I ran the cmdlet twice so you could see the default tabular view as well as the more detailed view. Again, you could easily use the Export-Csv cmdlet to dump this information to a file that you can provide your end-users.
I hope you find this cmdlet useful – it personally has proven invaluable to me, particularly when working on anonymous access internet sites as end-users are notorious about creating pages and not getting them checked in.
P.S. With this release the Publish-SPListItems cmdlet has been updated to now consider files that don’t have any existing check-ins.
Retrieving and Configuring the SharePoint 2010 Developer Dashboard using PowerShell
It’s been almost a year to the day since I’ve released my SharePoint 2010 cmdlets and, despite many good intentions to get them documented on my blog, things have just fallen by the wayside; this was primarily due to me going out on my own and writing my first book – but now that the book is done and I’ve begun to establish myself as an independent consultant, I believe it’s about time I start blogging about all these hidden cmdlets that I’ve created. So, to start I’m going to take a couple of cmdlets that I originally developed for some conference presentations; specifically Get-SPDeveloperDashboard and Set-SPDeveloperDashboard.
Before I show these two new cmdlets, let’s look at what it currently takes to retrieve and manipulate the developer dashboard using Windows PowerShell:
As you can see from the preceding figure, you obtain an instance of the SPDeveloperDashboardSettings object via the DeveloperDashboardSettings property of an SPWebService instance (obtained using the static ContentService property of the SPWebService class). Note that there are several properties that we can manipulate beyond just the simple DisplayLevel property that is used to enable or disable the developer dashboard (or to put it into on demand mode). Some people still like to use STSADM to change the DisplayLevel property but doing so doesn’t allow you to manipulate the other properties available; often the reason people use STSADM is because it’s slightly less verbose if all you wish to do is change the DisplayLevel property. Here’s an example of how you would do it with PowerShell:
$dds = [Microsoft.SharePoint.Administration.SPWebService]::ContentService.DeveloperDashboardSettings $dds.DisplayLevel = "On" $dds.Update()
So, not a whole lot of code but still more than the single STSADM line (that and people have a hard time remembering the full object path to get to the SPDeveloperDashboardSettings object – I personally can remember this easier than the STSADM key names).
Because of this slightly higher level of complexity I decided to create these cmdlets, but I also went ahead and added some PowerShell type extensions so that I could get to the developer dashboard from an SPFarm instance. I’ll examine that before we get into the cmdlets; if you download my source code you should notice a file named Lapointe.SharePoint2010.Automation.Cmdlets.Types.ps1xml in the {Project Root}\PowerShell\Types folder. Here’s the relevant contents of that file:
<?xml version="1.0" encoding="utf-8"?> <Types> <Type> <Name>Microsoft.SharePoint.Administration.SPFarm</Name> <Members> <ScriptProperty> <Name>DeveloperDashboard</Name> <GetScriptBlock>[Microsoft.SharePoint.Administration.SPWebService]::ContentService.DeveloperDashboardSettings</GetScriptBlock> </ScriptProperty> </Members> </Type> </Types>
What I’ve done here is essentially create a type extension using XML; the <Name /> element defines the full type name that you want to extend and the <Members /> element contains all the extensions. In this case I’ve added a new property named DeveloperDashboard and I provided the same script we saw previously so that the SPDeveloperDashboardSettings object will be returned. It’s important to understand that you are not limited to just get properties – you can create set properties as well as methods (type help about_types for more information about creating type extensions). With this type extension added we can now access the developer dashboard in a slightly simpler manner:
$dds = (Get-SPFarm).DeveloperDashboard
Using this approach there really isn’t a need for the Get-SPDeveloperDashboard cmdlet that I created, as the cmdlet only saves about seven characters; however, this approach isn’t obvious – what I want is users to be able to type Get-Command *dashboard* so that they can see all the cmdlets related to the developer dashboard. (Plus, I created the cmdlet originally just for demonstration purposes but it does make things a little more obvious). So now that we have the type extension out of the way, let’s take a look at the cmdlet. Here’s a dump of the full help for the Get-SPDeveloperDashboard cmdlet:
PS C:\> help Get-SPDeveloperDashboard -Full
NAME
Get-SPDeveloperDashboard
SYNOPSIS
Retrieves the Developer Dashboard Settings object.
SYNTAX
Get-SPDeveloperDashboard [-AssignmentCollection <spassignmentcollection>] [<commonparameters>]
DESCRIPTION
Retrieves the Developer Dashboard Settings object.
Copyright 2010 Falchion Consulting, LLC
> For more information on this cmdlet and others:
> http://blog.falchionconsulting.com/
> Use of this cmdlet is at your own risk.
> Gary Lapointe assumes no liability.
PARAMETERS
-AssignmentCollection [<spassignmentcollection>]
Manages objects for the purpose of proper disposal. Use of objects, such as SPWeb or SPSite,
can use large amounts of memory and use of these objects in Windows PowerShell scripts requires
proper memory management. Using the SPAssignment object, you can assign objects to a variable
and dispose of the objects after they are needed to free up memory. When SPWeb, SPSite, or
SPSiteAdministration objects are used, the objects are automatically disposed of if an assignment
collection or the Global parameter is not used.
When the Global parameter is used, all objects are contained in the global store. If objects are
not immediately used, or disposed of by using the Stop-SPAssignment command, an out-of-memory
scenario can occur.
Required? false
Position? named
Default value
Accept pipeline input? true (ByValue)
Accept wildcard characters? false
<commonparameters>
This cmdlet supports the common parameters: Verbose, Debug, ErrorAction, ErrorVariable,
WarningAction, WarningVariable, OutBuffer and OutVariable. For more information,
type, "get-help about_commonparameters".
INPUTS
OUTPUTS
NOTES
For more information, type "Get-Help Get-SPDeveloperDashboard -detailed".
For technical information, type "Get-Help Get-SPDeveloperDashboard -full".
------------------EXAMPLE------------------
PS C:\> $dash = Get-SPDeveloperDashboard
This example returns back the developer dashboard settings object.
RELATED LINKS
Set-SPDeveloperDashboard
So obviously the cmdlet is pretty simple as there aren’t any parameters beyond the standard parameters (remember, the -AssignmentCollection parameter is included as part of the cmdlet base class but as the SPDeveloperDashboardSettings object is not disposable there is no reason to use it.
The code for this cmdlet is actually shorter than the help for it:
using System.Collections.Generic; using System.Management.Automation; using Lapointe.PowerShell.MamlGenerator.Attributes; using Microsoft.SharePoint.PowerShell; using Microsoft.SharePoint.Administration; namespace Lapointe.SharePoint2010.Automation.Cmdlets.Farm { [Cmdlet(VerbsCommon.Get, "SPDeveloperDashboard", SupportsShouldProcess = false), SPCmdlet(RequireLocalFarmExist = true, RequireUserFarmAdmin = false)] [CmdletDescription("Retrieves the Developer Dashboard Settings object.")] [RelatedCmdlets(typeof(SPCmdletSetDeveloperDashboard))] [Example(Code = "PS C:\\> $dash = Get-SPDeveloperDashboard", Remarks = "This example returns back the developer dashboard settings object.")] public class SPCmdletGetDeveloperDashboard : SPGetCmdletBaseCustom<SPDeveloperDashboardSettings> { protected override IEnumerable<SPDeveloperDashboardSettings> RetrieveDataObjects() { WriteObject(SPWebService.ContentService.DeveloperDashboardSettings); return null; } } }
The following figure shows how you can call the cmdlet:
Note that I’ve also added a new view for the SPDeveloperDashboardSettings object type (as shown in the first example – to see all the properties use the Select-Object cmdlet as shown in the second example). The custom views are added just like the custom type extensions – for views, however, you create another XML file which you can see in my source code under the {Project Root}\PowerShell\Format folder. The following XML snippet illustrates the relevant portion of that file:
<?xml version="1.0" encoding="utf-8"?> <Configuration> <ViewDefinitions> <View> <Name>SPDeveloperDashboardSettings</Name> <ViewSelectedBy> <TypeName>Microsoft.SharePoint.Administration.SPDeveloperDashboardSettings</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader> <Label>Display Level</Label> <Width>14</Width> <Alignment>left</Alignment> </TableColumnHeader> <TableColumnHeader> <Label>Trace Enabled</Label> <Width>14</Width> <Alignment>left</Alignment> </TableColumnHeader> <TableColumnHeader> <Label>Required Permissions</Label> <Alignment>left</Alignment> </TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <PropertyName>DisplayLevel</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>TraceEnabled</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>RequiredPermissions</PropertyName> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> </ViewDefinitions> </Configuration>
Okay, so we’ve made it easier to retrieve the developer dashboard, now I want to change the values in one step (because retrieving the object, changing the value, and calling Update() is just too much work). To do this I created the Set-SPDeveloperDashboard cmdlet. This cmdlet is a bit more complex in that I’ve exposed all the relevant properties of the SPDeveloperDashboardSettings object with an equivalent parameter. Here’s the full help for the cmdlet:
PS C:\> help Set-SPDeveloperDashboard -Full
NAME
Set-SPDeveloperDashboard
SYNOPSIS
Sets the Developer Dashboard Settings.
SYNTAX
Set-SPDeveloperDashboard [-AutoLaunchEnabled <Boolean>] [-DisplayLevel <Off | OnDemand | On>] [-MaximumCriticalEventsToTrack <Int32>]
[-MaximumSQLQueriesToTrack <Int32>] [-RequiredPermissions <EmptyMask | ViewListItems | AddListItems | EditListItems | DeleteListItems |
ApproveItems | OpenItems | ViewVersions | DeleteVersions | CancelCheckout | ManagePersonalViews | ManageLists | ViewFormPages | Open |
ViewPages | AddAndCustomizePages | ApplyThemeAndBorder | ApplyStyleSheets | ViewUsageData | CreateSSCSite | ManageSubwebs | CreateGroups
| ManagePermissions | BrowseDirectories | BrowseUserInfo | AddDelPrivateWebParts | UpdatePersonalWebParts | ManageWeb |
UseClientIntegration | UseRemoteAPIs | ManageAlerts | CreateAlerts | EditMyUserInfo | EnumeratePermissions | FullMask>]
[-TraceEnabled <Boolean>] [-AdditionalEventsToTrack <String[]>] [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]
DESCRIPTION
Sets the Developer Dashboard Settings.
Copyright 2010 Falchion Consulting, LLC
> For more information on this cmdlet and others:
> http://blog.falchionconsulting.com/
> Use of this cmdlet is at your own risk.
> Gary Lapointe assumes no liability.
PARAMETERS
-AutoLaunchEnabled [<Boolean>]
Indicates whether the developer dashboard can be auto launched.
Required? false
Position? named
Default value
Accept pipeline input? false
Accept wildcard characters? false
-DisplayLevel [<SPDeveloperDashboardLevel>]
Indicates whether the developer dashboard is set to Off, On, or On Demand.
Required? false
Position? named
Default value
Accept pipeline input? false
Accept wildcard characters? false
-MaximumCriticalEventsToTrack [<Int32>]
The maximum number of critical events and asserts that will be recorded in a single transaction (i.e. one request or timer job).
If a single transaction has more than this number of asserts the remainder will be ignored. This can be set to 0 to disable
assert tracking.
Required? false
Position? named
Default value
Accept pipeline input? false
Accept wildcard characters? false
-MaximumSQLQueriesToTrack [<Int32>]
The maximum number of SQL queries that will be recorded in a single transaction (i.e. one request or timer job). If a single
transaction executes more than this number of requests the query will be counted but the query call stack and text will not be kept.
Required? false
Position? named
Default value
Accept pipeline input? false
Accept wildcard characters? false
-RequiredPermissions [<SPBasePermissions>]
A permission mask defining the permissions required to see the developer dashboard. This defaults to SPBasePermissions.AddAndCustomizePages.
Required? false
Position? named
Default value
Accept pipeline input? false
Accept wildcard characters? false
-TraceEnabled [<Boolean>]
Whether a link to display full verbose trace will be available at the bottom of the page when the developer dashboard is launched or not.
Required? false
Position? named
Default value
Accept pipeline input? false
Accept wildcard characters? false
-AdditionalEventsToTrack [<String[]>]
A list of URL tags to track in addition to events with severity above High.
Required? false
Position? named
Default value
Accept pipeline input? false
Accept wildcard characters? false
-AssignmentCollection [<SPAssignmentCollection>]
Manages objects for the purpose of proper disposal. Use of objects, such as SPWeb or SPSite,
can use large amounts of memory and use of these objects in Windows PowerShell scripts requires
proper memory management. Using the SPAssignment object, you can assign objects to a variable
and dispose of the objects after they are needed to free up memory. When SPWeb, SPSite, or
SPSiteAdministration objects are used, the objects are automatically disposed of if an assignment
collection or the Global parameter is not used.
When the Global parameter is used, all objects are contained in the global store. If objects are
not immediately used, or disposed of by using the Stop-SPAssignment command, an out-of-memory
scenario can occur.
Required? false
Position? named
Default value
Accept pipeline input? true (ByValue)
Accept wildcard characters? false
<CommonParameters>
This cmdlet supports the common parameters: Verbose, Debug,
ErrorAction, ErrorVariable, WarningAction, WarningVariable,
OutBuffer and OutVariable. For more information, type,
"get-help about_commonparameters".
INPUTS
OUTPUTS
NOTES
For more information, type "Get-Help Set-SPDeveloperDashboard -detailed". For technical information,
type "Get-Help Set-SPDeveloperDashboard -full".
------------------EXAMPLE 1-----------------------
PS C:\> Set-SPDeveloperDashboard -RequiredPermissions "ManageWeb,ManageSubwebs"
This example sets the required permissions to view the developer dashboard.
------------------EXAMPLE 2-----------------------
PS C:\> Set-SPDeveloperDashboard -DisplayLevel OnDemand -TraceEnabled $true
This example enables the developer dashboard.
RELATED LINKS
Get-SPDeveloperDashboard
The code for this cmdlet is obviously going to be slightly longer than the Get-SPDeveloperDashboard cmdlet, but again, it’s very simple as most of the code is just for defining the parameters:
using System.Collections.Generic; using System.Management.Automation; using Lapointe.PowerShell.MamlGenerator.Attributes; using Microsoft.SharePoint; using Microsoft.SharePoint.PowerShell; using Microsoft.SharePoint.Administration; namespace Lapointe.SharePoint2010.Automation.Cmdlets.Farm { [Cmdlet(VerbsCommon.Set, "SPDeveloperDashboard", SupportsShouldProcess = false), SPCmdlet(RequireLocalFarmExist = true, RequireUserFarmAdmin = false)] [CmdletDescription("Sets the Developer Dashboard Settings.")] [RelatedCmdlets(typeof(SPCmdletGetDeveloperDashboard))] [Example(Code = "PS C:\\> Set-SPDeveloperDashboard -DisplayLevel OnDemand -TraceEnabled $true", Remarks = "This example enables the developer dashboard.")] [Example(Code = "PS C:\\> Set-SPDeveloperDashboard -RequiredPermissions \"ManageWeb,ManageSubwebs\"", Remarks = "This example sets the required permissions to view the developer dashboard.")] public class SPCmdletSetDeveloperDashboard : SPSetCmdletBaseCustom<SPDeveloperDashboardSettings> { public SPCmdletSetDeveloperDashboard() { SPDeveloperDashboardSettings dash = SPWebService.ContentService.DeveloperDashboardSettings; AutoLaunchEnabled = dash.AutoLaunchEnabled; DisplayLevel = dash.DisplayLevel; MaximumCriticalEventsToTrack = dash.MaximumCriticalEventsToTrack; MaximumSQLQueriesToTrack = dash.MaximumSQLQueriesToTrack; RequiredPermissions = dash.RequiredPermissions; TraceEnabled = dash.TraceEnabled; AdditionalEventsToTrack = ((List<string>) dash.AdditionalEventsToTrack).ToArray(); } [Parameter(HelpMessage = "Indicates whether the developer dashboard can be auto launched.")] public bool AutoLaunchEnabled { get; set; } [Parameter(HelpMessage = "Indicates whether the developer dashboard is set to Off, On, or On Demand.")] public SPDeveloperDashboardLevel DisplayLevel { get; set; } [Parameter(HelpMessage = "The maximum number of critical events and asserts that will be recorded in a single transaction (i.e. one request or timer job). If a single transaction has more than this number of asserts the remainder will be ignored. This can be set to 0 to disable assert tracking.")] public int MaximumCriticalEventsToTrack { get; set; } [Parameter(HelpMessage = "The maximum number of SQL queries that will be recorded in a single transaction (i.e. one request or timer job). If a single transaction executes more than this number of requests the query will be counted but the query call stack and text will not be kept. ")] public int MaximumSQLQueriesToTrack { get; set; } [Parameter(HelpMessage = "A permission mask defining the permissions required to see the developer dashboard. This defaults to SPBasePermissions.AddAndCustomizePages.")] public SPBasePermissions RequiredPermissions { get; set; } [Parameter(HelpMessage = "Whether a link to display full verbose trace will be available at the bottom of the page when the developer dashboard is launched or not.")] public bool TraceEnabled { get; set; } [Parameter(HelpMessage = "A list of URL tags to track in addition to events with severity above High. ")] public string[] AdditionalEventsToTrack { get; set; } protected override void UpdateDataObject() { SPDeveloperDashboardSettings dash = SPWebService.ContentService.DeveloperDashboardSettings; dash.AutoLaunchEnabled = AutoLaunchEnabled; dash.DisplayLevel = DisplayLevel; dash.MaximumCriticalEventsToTrack = MaximumCriticalEventsToTrack; dash.MaximumSQLQueriesToTrack = MaximumSQLQueriesToTrack; dash.RequiredPermissions = RequiredPermissions; dash.TraceEnabled = TraceEnabled; dash.AdditionalEventsToTrack.Clear(); ((List<string>)dash.AdditionalEventsToTrack).AddRange(AdditionalEventsToTrack); dash.Update(); } } }
The following figure shows how you can call the cmdlet:
So that wraps up my first (very long overdue) post for my 2010 cmdlets – look for more posts coming soon as well as an update to my index page listing all the available cmdlets.
-Gary
Deploying SharePoint 2010 Solution Package Using PowerShell (Revisited)
If you were at my PowerShell for developers talk at the European SharePoint Best Practices Conference last week then you’ll know that I’ve never been all that happy with how I was approaching Farm Solution deployment, as detailed in an earlier post from sometime last year (Deploying SharePoint 2010 Solution Packages Using PowerShell). So what are some of the issues I have with what I created? Here’s a quick list:
- There are two functions – I really just want one function to call and let the function figure out what to do based on the parameters provided.
- I want to be able to provide a directory containing WSP files to deploy (sure, I could use Get-ChildItem to grab all my files, iterate through them, and then call the function, but that means I have to type more each time I want to execute and I’m way too lazy for that).
- There’s no consideration for simply updating Solution Packages rather than retracting and redeploying.
- I was using the Start-SPAdminJob cmdlet and stopping and starting the admin service – something that we shouldn’t be doing and is really just an old throwback to 2007. It’s just a bad idea – don’t do it.
- I was forcing information such as GAC and CAS settings in the XML when I could easily get the information via the SPSolution object once added.
- And finally, there was no real help available so you had to really know what was going on to understand how to construct the XML file and to then call the file.
For all these reasons I’ve decided to completely rewrite the script. As a result it’s a bit more complicated at first blush but that’s mainly due to some additional error handling, progress reporting, and blocking code that I’ve added; as well as the additional parameter related code and associated help. I’ve essentially followed the pattern that I described with my earlier post on Feature activation and have made the function work more like a cmdlet (with full help, parameter sets, and use of PipeBind objects). Before I share the code, I’d like to show the complete help that is available for the function:
NAME
Deploy-SPSolutions
SYNOPSIS
Deploys one or more Farm Solution Packages to the Farm.
SYNTAX
Deploy-SPSolutions [-Identity] <String> [[-UpgradeExisting]] [[-AllWebApplications]] [[-WebApplication] <SPWebApplicationPipeBind[]>] [<CommonParameters>]
Deploy-SPSolutions [-Config] <XmlDocument> [<CommonParameters>]
DESCRIPTION
Specify either a directory containing WSP files, a single WSP file, or an XML configuration file containing the WSP files to deploy.
If using an XML configuration file, the format of the file must match the following:
<Solutions>
<Solution Path="<full path and filename to WSP>" UpgradeExisting="false">
<WebApplications>
<WebApplication>http://example.com/</WebApplication>
</WebApplications>
</Solution>
</Solutions>
Multiple <Solution> and <WebApplication> nodes can be added. The UpgradeExisting attribute is optional and should be specified if the WSP should be udpated and not retracted and redeployed.
PARAMETERS
-Config <XmlDocument>
The XML configuration file containing the WSP files to deploy.
Required? true
Position? 1
Default value
Accept pipeline input? false
Accept wildcard characters?
-Identity <String>
The directory, WSP file, or XML configuration file containing the WSP files to deploy.
Required? true
Position? 1
Default value
Accept pipeline input? false
Accept wildcard characters?
-UpgradeExisting [<SwitchParameter>]
If specified, the WSP file(s) will be updated and not retracted and redeployed (if the WSP does not exist in the Farm then this parameter has no effect).
Required? false
Position? 2
Default value
Accept pipeline input? false
Accept wildcard characters?
-AllWebApplications [<SwitchParameter>]
If specified, the WSP file(s) will be deployed to all Web Applications in the Farm (if applicable).
Required? false
Position? 3
Default value
Accept pipeline input? false
Accept wildcard characters?
-WebApplication <SPWebApplicationPipeBind[]>
Specifies the Web Application(s) to deploy the WSP file to.
Required? false
Position? 4
Default value
Accept pipeline input? false
Accept wildcard characters?
<CommonParameters>
This cmdlet supports the common parameters: Verbose, Debug,
ErrorAction, ErrorVariable, WarningAction, WarningVariable,
OutBuffer and OutVariable. For more information, type,
"get-help about_commonparameters".
INPUTS
OUTPUTS
-------------------------- EXAMPLE 1 --------------------------
PS C:\>. .\Deploy-SPSolutions.ps1
PS C:\> Deploy-SPSolutions -Identity C:\WSPs -WebApplication http://demo
This example loads the function into memory and then deploys all the WSP files in the specified directory to the http://demo Web Application (if applicable).
-------------------------- EXAMPLE 2 --------------------------
PS C:\>. .\Deploy-SPSolutions.ps1
PS C:\> Deploy-SPSolutions -Identity C:\WSPs -WebApplication http://demo,http://mysites
This example loads the function into memory and then deploys all the WSP files in the specified directory to the http://demo and http://mysites Web Applications (if applicable).
-------------------------- EXAMPLE 3 --------------------------
PS C:\>. .\Deploy-SPSolutions.ps1
PS C:\> Deploy-SPSolutions -Identity C:\WSPs -AllWebApplications
This example loads the function into memory and then deploys all the WSP files in the specified directory to all Web Applications (if applicable).
-------------------------- EXAMPLE 4 --------------------------
PS C:\>. .\Deploy-SPSolutions.ps1
PS C:\> Deploy-SPSolutions -Identity C:\WSPs\MyCustomSolution.wsp -AllWebApplications
This example loads the function into memory and then deploys the specified WSP to all Web Applications (if applicable).
-------------------------- EXAMPLE 5 --------------------------
PS C:\>. .\Deploy-SPSolutions.ps1
PS C:\> Deploy-SPSolutions -Identity C:\WSPs\MyCustomSolution.wsp -AllWebApplications -UpgradeExisting
This example loads the function into memory and then deploys the specified WSP to all Web Applications (if applicable); existing deployments will be upgraded and not retracted and redeployed.
-------------------------- EXAMPLE 6 --------------------------
PS C:\>. .\Deploy-SPSolutions.ps1
PS C:\> Deploy-SPSolutions C:\Solutions.xml
This example loads the function into memory and then deploys all the WSP files specified by the Solutions.xml configuration file.
RELATED LINKS
Get-Content
Get-SPSolution
Add-SPSolution
Install-SPSolution
Update-SPSolution
Uninstall-SPSolution
Remove-SPSolution
As you can see, this is a lot more useful for someone wishing to execute this script as not only does it provide information about the XML structure but it also provides several usage examples.
So, without further delay, here’s the new version of the deployment script (note that I changed the function name to Deploy-SPSolutions so it won’t impact environments that depend on the old function):
function global:Deploy-SPSolutions() { <# .Synopsis Deploys one or more Farm Solution Packages to the Farm. .Description Specify either a directory containing WSP files, a single WSP file, or an XML configuration file containing the WSP files to deploy. If using an XML configuration file, the format of the file must match the following: <Solutions> <Solution Path="<full path and filename to WSP>" UpgradeExisting="false"> <WebApplications> <WebApplication>http://example.com/</WebApplication> </WebApplications> </Solution> </Solutions> Multiple <Solution> and <WebApplication> nodes can be added. The UpgradeExisting attribute is optional and should be specified if the WSP should be udpated and not retracted and redeployed. .Example PS C:\> . .\Deploy-SPSolutions.ps1 PS C:\> Deploy-SPSolutions -Identity C:\WSPs -WebApplication http://demo This example loads the function into memory and then deploys all the WSP files in the specified directory to the http://demo Web Application (if applicable). .Example PS C:\> . .\Deploy-SPSolutions.ps1 PS C:\> Deploy-SPSolutions -Identity C:\WSPs -WebApplication http://demo,http://mysites This example loads the function into memory and then deploys all the WSP files in the specified directory to the http://demo and http://mysites Web Applications (if applicable). .Example PS C:\> . .\Deploy-SPSolutions.ps1 PS C:\> Deploy-SPSolutions -Identity C:\WSPs -AllWebApplications This example loads the function into memory and then deploys all the WSP files in the specified directory to all Web Applications (if applicable). .Example PS C:\> . .\Deploy-SPSolutions.ps1 PS C:\> Deploy-SPSolutions -Identity C:\WSPs\MyCustomSolution.wsp -AllWebApplications This example loads the function into memory and then deploys the specified WSP to all Web Applications (if applicable). .Example PS C:\> . .\Deploy-SPSolutions.ps1 PS C:\> Deploy-SPSolutions -Identity C:\WSPs\MyCustomSolution.wsp -AllWebApplications -UpgradeExisting This example loads the function into memory and then deploys the specified WSP to all Web Applications (if applicable); existing deployments will be upgraded and not retracted and redeployed. .Example PS C:\> . .\Deploy-SPSolutions.ps1 PS C:\> Deploy-SPSolutions C:\Solutions.xml This example loads the function into memory and then deploys all the WSP files specified by the Solutions.xml configuration file. .Parameter Config The XML configuration file containing the WSP files to deploy. .Parameter Identity The directory, WSP file, or XML configuration file containing the WSP files to deploy. .Parameter UpgradeExisting If specified, the WSP file(s) will be updated and not retracted and redeployed (if the WSP does not exist in the Farm then this parameter has no effect). .Parameter AllWebApplications If specified, the WSP file(s) will be deployed to all Web Applications in the Farm (if applicable). .Parameter WebApplication Specifies the Web Application(s) to deploy the WSP file to. .Link Get-Content Get-SPSolution Add-SPSolution Install-SPSolution Update-SPSolution Uninstall-SPSolution Remove-SPSolution #> [CmdletBinding(DefaultParameterSetName="FileOrDirectory")] param ( [Parameter(Mandatory=$true, Position=0, ParameterSetName="Xml")] [ValidateNotNullOrEmpty()] [xml]$Config, [Parameter(Mandatory=$true, Position=0, ParameterSetName="FileOrDirectory")] [ValidateNotNullOrEmpty()] [string]$Identity, [Parameter(Mandatory=$false, Position=1, ParameterSetName="FileOrDirectory")] [switch]$UpgradeExisting, [Parameter(Mandatory=$false, Position=2, ParameterSetName="FileOrDirectory")] [switch]$AllWebApplications, [Parameter(Mandatory=$false, Position=3, ParameterSetName="FileOrDirectory")] [Microsoft.SharePoint.PowerShell.SPWebApplicationPipeBind[]]$WebApplication ) function Block-SPDeployment($solution, [bool]$deploying, [string]$status, [int]$percentComplete) { do { Start-Sleep 2 Write-Progress -Activity "Deploying solution $($solution.Name)" -Status $status -PercentComplete $percentComplete $solution = Get-SPSolution $solution if ($solution.LastOperationResult -like "*Failed*") { throw "An error occurred during the solution retraction, deployment, or update." } if (!$solution.JobExists -and (($deploying -and $solution.Deployed) -or (!$deploying -and !$solution.Deployed))) { break } } while ($true) sleep 5 } switch ($PsCmdlet.ParameterSetName) { "Xml" { # An XML document was provided so iterate through all the defined solutions and call the other parameter set version of the function $Config.Solutions.Solution | ForEach-Object { [string]$path = $_.Path [bool]$upgrade = $false if (![string]::IsNullOrEmpty($_.UpgradeExisting)) { $upgrade = [bool]::Parse($_.UpgradeExisting) } $webApps = $_.WebApplications.WebApplication Deploy-SPSolutions -Identity $path -UpgradeExisting:$upgrade -WebApplication $webApps -AllWebApplications:$(($webApps -eq $null) -or ($webApps.Length -eq 0)) } break } "FileOrDirectory" { $item = Get-Item (Resolve-Path $Identity) if ($item -is [System.IO.DirectoryInfo]) { # A directory was provided so iterate through all files in the directory and deploy if the file is a WSP (based on the extension) Get-ChildItem $item | ForEach-Object { if ($_.Name.ToLower().EndsWith(".wsp")) { Deploy-SPSolutions -Identity $_.FullName -UpgradeExisting:$UpgradeExisting -WebApplication $WebApplication } } } elseif ($item -is [System.IO.FileInfo]) { # A specific file was provided so assume that the file is a WSP if it does not have an XML extension. [string]$name = $item.Name if ($name.ToLower().EndsWith(".xml")) { Deploy-SPSolutions -Config ([xml](Get-Content $item.FullName)) return } $solution = Get-SPSolution $name -ErrorAction SilentlyContinue if ($solution -ne $null -and $UpgradeExisting) { # Just update the solution, don't retract and redeploy. Write-Progress -Activity "Deploying solution $name" -Status "Updating $name" -PercentComplete -1 $solution | Update-SPSolution -CASPolicies:$($solution.ContainsCasPolicy) ` -GACDeployment:$($solution.ContainsGlobalAssembly) ` -LiteralPath $item.FullName Block-SPDeployment $solution $true "Updating $name" -1 Write-Progress -Activity "Deploying solution $name" -Status "Updated" -Completed return } if ($solution -ne $null) { #Retract the solution if ($solution.Deployed) { Write-Progress -Activity "Deploying solution $name" -Status "Retracting $name" -PercentComplete 0 if ($solution.ContainsWebApplicationResource) { $solution | Uninstall-SPSolution -AllWebApplications -Confirm:$false } else { $solution | Uninstall-SPSolution -Confirm:$false } #Block until we're sure the solution is no longer deployed. Block-SPDeployment $solution $false "Retracting $name" 12 Write-Progress -Activity "Deploying solution $name" -Status "Solution retracted" -PercentComplete 25 } #Delete the solution Write-Progress -Activity "Deploying solution $name" -Status "Removing $name" -PercentComplete 30 Get-SPSolution $name | Remove-SPSolution -Confirm:$false Write-Progress -Activity "Deploying solution $name" -Status "Solution removed" -PercentComplete 50 } #Add the solution Write-Progress -Activity "Deploying solution $name" -Status "Adding $name" -PercentComplete 50 $solution = Add-SPSolution $item.FullName Write-Progress -Activity "Deploying solution $name" -Status "Solution added" -PercentComplete 75 #Deploy the solution if (!$solution.ContainsWebApplicationResource) { Write-Progress -Activity "Deploying solution $name" -Status "Installing $name" -PercentComplete 75 $solution | Install-SPSolution -GACDeployment:$($solution.ContainsGlobalAssembly) -CASPolicies:$($solution.ContainsCasPolicy) -Confirm:$false Block-SPDeployment $solution $true "Installing $name" 85 } else { if ($WebApplication -eq $null -or $WebApplication.Length -eq 0) { Write-Progress -Activity "Deploying solution $name" -Status "Installing $name to all Web Applications" -PercentComplete 75 $solution | Install-SPSolution -GACDeployment:$($solution.ContainsGlobalAssembly) -CASPolicies:$($solution.ContainsCasPolicy) -AllWebApplications -Confirm:$false Block-SPDeployment $solution $true "Installing $name to all Web Applications" 85 } else { $WebApplication | ForEach-Object { $webApp = $_.Read() Write-Progress -Activity "Deploying solution $name" -Status "Installing $name to $($webApp.Url)" -PercentComplete 75 $solution | Install-SPSolution -GACDeployment:$gac -CASPolicies:$cas -WebApplication $webApp -Confirm:$false Block-SPDeployment $solution $true "Installing $name to $($webApp.Url)" 85 } } } Write-Progress -Activity "Deploying solution $name" -Status "Deployed" -Completed } break } } }
When it comes to using the function I believe the help documentation speaks for itself so I won’t reiterate it here.
As always, I’m open to suggestions as to how to improve this function so please leave a comment if you find something wrong or have a suggestion for making it better.
-Gary
Retrieving SharePoint 2010 Feature Activations Using Windows PowerShell
During my PowerShell for Developers presentation in London last week I promised to show and demonstrate a script for retrieving Feature activations; unfortunately I ran out of time and was not able to show this script to the degree that I’d intended so I decided to throw together this blog post.
When developing custom Features it is very common to expect that there will need to be some level of update required for those Features. Typically this means that, after deploying the Feature via a Solution Package, you will need to re-activate that Feature in order to trigger any additional code to run (or, if you are using the new SharePoint 2010 Feature upgrade capabilities you will need to run the Upgrade(Boolean) method of the SPFeature object). The problem is knowing where the Feature is activated throughout the Farm. Using PowerShell there are two ways to do this – you can use the Get-SPFeature cmdlet and test the results against the appropriate scope or you can use the various “Query” methods that have been provided for each scope. I don’t recommend that you use the Get-SPFeature cmdlet as it is very inefficient, and as such, I won’t bother showing an example of that here. Instead I’ll focus on the “Query” methods approach.
Whether your Feature is scoped to the Farm, Web Application, Site Collection, or Site, there is a method that you can call to get an SPFeature object which effectively corresponds to a Feature activation. For Farm scoped Features you use the QueryFeatures(Guid, Boolean) method of the SPWebService class, obtainable via the SPWebService class’ static AdministrationService property; for Web Application scoped Features you use the static QueryFeaturesInAllWebServices(Guid, Boolean) method of the SPWebService class; for Site Collection scoped Features you use the QueryFeatures(Guid, Boolean) method of the SPWebApplication class; and for Site scoped Features you use the QueryFeatures(Guid, Boolean) method of the SPSite class.
To create our PowerShell function we’ll simply take in a SPFeatureDefinition object and use a switch statement to call the appropriate method based on the scope of the Feature. To make the function more versatile we can use the Microsoft.SharePoint.PowerShell.SPFeatureDefinitionPipeBind type which will allow the caller to pass in either the name of the Feature, its ID, or an actual SPFeatureDefinition object; additionally, we can use parameter attributes to easily allow the value to be passed in via the object pipeline. And finally, we’ll add an additional parameter stating that we wish to retrieve only those activations that require upgrading and we’ll add some basic help for the function.
The following code listing represents the completed function – I recommend that you save this to a file named Get-SPFeatureActivations.ps1. Note that I plan on adding this as a cmdlet to my downloadable extensions thereby making the need for this script unnecessary, however, I believe that this example provides a great template to use for creating professional looking, production ready scripts that both IT administrators and developers can use.
function Get-SPFeatureActivations() { <# .Synopsis Retrieves Feature activations for the given Feature Definition. .Description Retrieves the SPFeature object for each activation of the SPFeatureDefinition object. .Example Get-SPFeatureActivations TeamCollab .Parameter Identity The Feature name, ID, or SPFeatureDefinition object whose activations will be retrieved. .Parameter NeedsUpgrade If specified, only Feature activations needing upgrading will be retrieved. .Link Get-SPFeature #> [CmdletBinding()] param ( [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] [Alias("Feature")] [ValidateNotNullOrEmpty()] [Microsoft.SharePoint.PowerShell.SPFeatureDefinitionPipeBind]$Identity, [Parameter(Mandatory=$false, Position=1)] [switch]$NeedsUpgrade ) begin { } process { $fd = $Identity.Read() switch ($fd.Scope) { "Farm" { [Microsoft.SharePoint.Administration.SPWebService]::AdministrationService.QueryFeatures($fd.ID, $NeedsUpgrade.IsPresent) break } "WebApplication" { [Microsoft.SharePoint.Administration.SPWebService]::QueryFeaturesInAllWebServices($fd.ID, $NeedsUpgrade.IsPresent) break } "Site" { foreach ($webApp in Get-SPWebApplication) { $webApp.QueryFeatures($fd.ID, $NeedsUpgrade.IsPresent) } break } "Web" { foreach ($site in Get-SPSite -Limit All) { $site.QueryFeatures($fd.ID, $NeedsUpgrade.IsPresent) $site.Dispose() } break } } } end { } }
Assuming you’ve saved the file to the root of the C drive (not recommended but its what I do when I’m doing demos) then you can load the function into memory using dot sourcing as shown in the following example (note that the help for the function shows the help information specified by the block comment help):
Once the function is loaded into memory you can start using it. In the following example I’m returning back all the locations where the MyCustomFeature Feature is activated; I then use the Select-Object cmdlet to return just the URL for each activation:
Get-SPFeatureActivations MyCustomFeature | select @{Expression={$_.Parent.Url};Label="Url"}In this next example, instead of simply outputting the URL of each activation, I’m forcing the Feature to be reactivated using the Enable-SPFeature cmdlet (use the -Force parameter to force the Feature to be reactivated – you could also change the code to deactivate the Feature using the Disable-SPFeature cmdlet and then activate using the Enable-SPFeature cmdlet):
Get-SPFeatureActivations MyCustomFeature | ForEach-Object {
Enable-SPFeature -Identity MyCustomFeature -Url $_.Parent.Url -Force
}
Similarly you can retrieve only those Features needing upgrade and then call the Upgrade() method, as shown in this next example:
Get-SPFeatureActivations MyCustomFeature -NeedsUpgrade | ForEach-Object {$_.Upgrade($false)
}
I strongly recommend that, before you re-deploy a Feature that may be activated at an unknown number of scopes, you run this function (or something similar to it) so that you fully understand the impact of upgrading your Feature. One more thing to watch out for, if your environment is very large you may wish to modify this function so that it does not return the SPFeature object but instead just returns the URL corresponding to the activation – you can then use the Get-SPFeature cmdlet to retrieve the SPFeature object; the benefit of this is that you can immediately dispose of the parent object and prevent potential out of memory errors (I’m particularly concerned with Site Collection and Site scoped Features here where the Parent property of the SPFeature object corresponds to an SPSite or SPWeb object which must be disposed).
That’s all I’ve got for now; hopefully you’ve found this useful!
-Gary
European SharePoint Best Practices Conference Wrap Up
I just got back from London and all I can say is, “Wow!” This was the first time that myself and my wife and daughter have ever been out of the US and we had an absolute blast – we did so much walking that we literally wore our shoes thin – it’s truly an amazing place with incredible history everywhere you turn.
As for the conference itself, first off I want to thank Steve Smith and all those that were involved with organizing such a wonderful conference – as a speaker they definitely set the bar for other conference organizers and I hope that attendees saw the same attention to detail and overall quality that the speakers saw (this conference is truly unique amongst all the ones I’ve spoken at).
In regards to my two sessions – I totally blew my timing on both of them (more so on the developer one) and was unable to show everything that I planned but I do hope that those that attended got some useful information for the time spent (virtually nobody left when I ran late so I’m going to take that as a good sign that people were getting value out of the presentations). I plan to post some of the sample scripts that I demonstrated during the presentations but that will come over the next few weeks; for now I’ve posted my slide decks (saved with notes so you can see some of my examples) for you to download:
| Windows PowerShell for SharePoint 2010 Administrators | |
| Windows PowerShell for SharePoint 2010 Developers |
Don’t forget that you can also download the PowerShell cheat sheet that I provided during the sessions (see my earlier post).
Thanks again to everyone that attended my sessions and for all the great tweets that came out during and after the sessions – I know I still have lots to learn when it comes to public speaking so any kind of feedback is very much appreciated!









