“Stamping” PDF Files Downloaded from SharePoint 2010
First off I want to clarify that the subject of this post is not my idea as it is something that my friend Roman Kobzarev put together for his company and I merely assisted with getting the code to work. The problem that Roman was trying to solve was that his company provided numerous PDF files that registered/paying members could download and, unfortunately, they were finding some of those files being posted to various other sites without their permission; so in an attempt to at least discourage this they wanted to stamp the PDF files with some information about the user who downloaded the file.
There are various ways in which this problem can be solved but perhaps one of the simpler approaches, and the approach outlined here, was to create an HTTP Handler which intercepted requests for any PDF files and then simply retrieve the file from SharePoint, modify it, and then send it on its way. The cool thing about this approach and the pattern shown here is that it can easily be applied to any file type which requires some user or request specific modifications applied to it.
Before I get to the actual code I want to point out one third party dependency that we used: iTextSharp. This is an open source .NET library for PDF generation and manipulation. There are many options available when looking to manipulate PDF files and in this case this one was chosen due to its cost (free). So let’s get to the code.
In terms of code there really isn’t that much which is what makes this such a nice solution. The first piece is the actual implementation of the IHttpHandler interface:
using System.IO; using System.Web; using Microsoft.SharePoint; using iTextSharp.text; using iTextSharp.text.pdf; namespace Aptillon.SharePoint.PDFWatermark { public class PDFWatermarkHttpHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { SPFile file = SPContext.Current.Web.GetFile(context.Request.Url.ToString()); byte[] content = file.OpenBinary(); SPUser currentUser = SPContext.Current.Web.CurrentUser; string watermark = null; if (currentUser != null) watermark = "This download was specially prepared for " + currentUser.Name; if (watermark != null) { PdfReader pdfReader = new PdfReader(content); using (MemoryStream outputStream = new MemoryStream()) using (PdfStamper pdfStamper = new PdfStamper(pdfReader, outputStream)) { for (int pageIndex = 1; pageIndex <= pdfReader.NumberOfPages; pageIndex++) { //Rectangle class in iText represent geometric representation... //in this case, rectangle object would contain page geometry Rectangle pageRectangle = pdfReader.GetPageSizeWithRotation(pageIndex); //PdfContentByte object contains graphics and text content of page returned by PdfStamper PdfContentByte pdfData = pdfStamper.GetUnderContent(pageIndex); //create font size for watermark pdfData.SetFontAndSize(BaseFont.CreateFont(BaseFont.HELVETICA_BOLD, BaseFont.CP1252, BaseFont.NOT_EMBEDDED), 8); //create new graphics state and assign opacity PdfGState graphicsState = new PdfGState(); graphicsState.FillOpacity = 0.4F; //set graphics state to PdfContentByte pdfData.SetGState(graphicsState); //indicates start of writing of text pdfData.BeginText(); //show text as per position and rotation pdfData.ShowTextAligned(Element.ALIGN_CENTER, watermark, pageRectangle.Width / 4, pageRectangle.Height / 44, 0); //call endText to invalid font set pdfData.EndText(); } pdfStamper.Close(); content = outputStream.ToArray(); } } context.Response.ContentType = "application/pdf"; context.Response.BinaryWrite(content); context.Response.End(); } public bool IsReusable { get { return false; } } } }
As you can see from the previous code listing, the bulk of the code involves the actual processing of the PDF file but the core SharePoint specific piece is in the beginning of the ProcessRequest() method where we use the SPContext.Current.Web.GetFile() method to retrieve the actual file requested and then, if we can get an actual SPUser object, we create a simple message that will be added to the bottom of the PDF. I’m not going to cover what’s happening with the iTextSharp objects as the point of this article is to demonstrate the pattern which can easily be applied to other file types and not how to use iTextSharp.
To deploy this class I created an empty SharePoint 2010 project using Visual Studio 2010 and added the file to the project. I then created a new Web Application scoped Feature which I use to add the appropriate web.config settings which will register the HTTP Handler. The following screenshot shows the final project structure:

Note that I also added the iTextSharp.dll to the project and added it as an additional assembly to the package by double clicking the Package.package element and then, in the package designer, click Advanced to add additional assemblies:

Before I show the code for the Web Application Feature I first want to show what settings I set for the Feature after adding it:

When you add a new Feature to a project it’s going to name it Feature1 and set the default scope to Web. The first thing I do is rename the Feature to something meaningful – in this case, because I know I’ll only have the one Feature I go ahead and name it the same as the project name: Aptillon.SharePoint.PDFWatermark (I always follow the same naming convention for my projects, which equate to WSP file names and Features: <Company>.SharePoint.<Something Appropriate for the Contained Functionality>). The next thing I do is change the Deployment Path property for the Feature so that it only uses the Feature name and does not prepend the project name; and finally I set the scope and title of the Feature. Now I’m ready to add my Feature activation event receiver.
The code that I want to include in the event receiver will handle the addition and removal of the web.config handler elements. I do this using the SPWebConfigModification class. Now there’s debate on whether this should be used or not; this is one of those classes where you might say (as my friend Spence Harbar puts it), “Just because you should use it doesn’t mean you can.” The simple explanation for this is that ideally you should be using this class to make web.config modifications but the reality is that this guy is fraught with issues and usually doesn’t work. That said, what I usually do is, where it makes sense, use this class to add and remove my entries but work under the premise that it will probably not work and plan on making these changes manually (or write a timer job which will do what this guy is attempting to do, but that’s out of scope of this article). So here’s the FeatureActivated() and FeatureDeactivating() methods that I use to add and remove the appropriate web.config entries which register the previously defined HTTP Handler:
public override void FeatureActivated(SPFeatureReceiverProperties properties) { string asmDetails = typeof(PDFWatermarkHttpHandler).AssemblyQualifiedName; SPWebApplication webApp = properties.Feature.Parent as SPWebApplication; if (webApp == null) return; SPWebConfigModification modification = new SPWebConfigModification("add[@name=\"PDFWatermark\"]", "configuration/system.webServer/handlers"); modification.Value = string.Format("<add name=\"PDFWatermark\" verb=\"*\" path=\"*.pdf\" type=\"{0}\" preCondition=\"integratedMode\" />", asmDetails); modification.Sequence = 1; modification.Owner = asmDetails; modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode; webApp.WebConfigModifications.Add(modification); webApp.Update(); webApp.WebService.ApplyWebConfigModifications(); } public override void FeatureDeactivating(SPFeatureReceiverProperties properties) { string asmDetails = typeof(PDFWatermarkHttpHandler).AssemblyQualifiedName; SPWebApplication webApp = properties.Feature.Parent as SPWebApplication; if (webApp == null) return; List<SPWebConfigModification> configModsFound = new List<SPWebConfigModification>(); Collection<SPWebConfigModification> modsCollection = webApp.WebConfigModifications; for (int i = 0; i < modsCollection.Count; i++) { if (modsCollection[i].Owner == asmDetails) { configModsFound.Add(modsCollection[i]); } } if (configModsFound.Count > 0) { foreach (SPWebConfigModification mod in configModsFound) modsCollection.Remove(mod); webApp.Update(); webApp.WebService.ApplyWebConfigModifications(); } }
With all the code in place I can deploy to my Web Application and now any time a PDF is downloaded I’ll have a nice little message displayed on the bottom of each page. Again, the intent here is to show the simplicity of the pattern and approach – with a little imagination you can easily come up with lots of other uses for this (applying security or password protection to the PDF, adding an image watermark, removing pages based on registration status thus providing “sample” versions, prefilling form fields with user data, adding a version history page, etc.). And of course all this can also be applied to other file types such as the Office files or images (though image handling would take a little more logic to ignore images not coming from document libraries).
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 2010 SP1 Public API Changes
I recently published a post detailing the SharePoint 2010 SP1 PowerShell changes and, in that post, I mentioned that I was probably going to detail the API changes. Well, here they are. Note that the list below is not a comprehensive one in that I’m not showing every assembly (I do have the changes for every assembly but frankly I just got tired of translating the results into a readable format so I kept it to the more prominent assemblies (or at least the ones that I just happened to have done at the time)). In reviewing the list you see that there’s honestly not a whole lot of noteworthy changes, but that’s okay as part of my reasoning for doing this was to discover whether there were any (don’t get me wrong, there are some, in fact, for me there’s 1 very big one that made this whole exercise worth it – I’ll let you figure out which one that is). If you find any I missed please add a comment so that others can see it as well.
- Microsoft.SharePoint.dll
- Microsoft.SharePoint.SPRecycleBinItemType
- New enum value:
- Web
- New enum value:
- Microsoft.SharePoint.SPWeb
- New method:
- public void Recycle()
- New method:
- Microsoft.SharePoint.Strings
- New constants:
- public const string CannotRecycleRootWeb
- public const string HealthRule_Explanation_BcsShimsAreEnabled
- public const string HealthRule_Remedy_BcsShimsAreEnabled
- public const string RecycleBinWebMissingContainerError
- public const string SPStorageMetricsProcessingJobDescription
- public const string SPUsageUserCodeRequestsDescription
- public const string SPUsageUserCodeRequestsMonitoredDataDescription
- public const string SiteAlreadyExists
- public const string StorageMetricsDBObjectsNotFound
- public const string StorageMetricsFreshnessWarning
- public const string StorageMetricsNotAvailable
- public const string TimerJobTitleStorageMetricsProcessing
- New constants:
- Microsoft.SharePoint.Administration.SPAce<T>
- New properties:
- public Byte[] BinaryId() { get; }
- public Microsoft.SharePoint.Administration.SPIdentifierType BinaryIdType() { get; }
- New properties:
- Microsoft.SharePoint.Administration.SPAcl<T>
- New method:
- public SPAce<T> Add(string principalName, string displayName, SPIdentifierType identifierType, byte[] identifier, T grantRightsMask, T denyRightsMask)
- New method:
- Microsoft.SharePoint.Administration.SPContentDatabase
- New methods:
- public Microsoft.SharePoint.Administration.SPDeletedSite GetDeletedSite(System.Guid id)
- public void Move(SPContentDatabase destinationDb, List<SPSite> sitesToMove, Dictionary<string, string> rbsProviderMap, out Dictionary<SPSite, string> failedSites)
- New methods:
- Microsoft.SharePoint.Administration.SPContentDatabaseCollection
- New method:
- public SPContentDatabase Add(string strDatabaseServer, string strDatabaseName, string strDatabaseUsername, string strDatabasePassword, int warningSiteCount, int maximumSiteCount, int status, bool flushChangeLog, bool changeSyncKnowledge)
- New method:
- Microsoft.SharePoint.Administration.SPDatabase
- New method:
- public void ChangeDatabaseInstance(string databaseServiceInstance)
- New method:
- Microsoft.SharePoint.Administration.SPIncomingEmailService
- New property:
- public int RetryDeliveryInterval { get; set; }
- New property:
- Microsoft.SharePoint.Administration.SPPolicy
- New method:
- protected Void OnDeserialization()
- New method:
- Microsoft.SharePoint.Administration.SPSiteLookupProvider
- Changed method (breaking change!)
- public Void RenameHostHeaderSite(Guid siteId, string newHostHeader) => public Void RenameHostHeaderSite(Guid siteId, Uri newHostHeaderSiteUri)
- Changed method (breaking change!)
- Microsoft.SharePoint.Administration.SPUsageApplication
- New property:
- public int UsageInsertionTimeOut { get; set; }
- New property:
- Microsoft.SharePoint.Administration.SPUserCodeExecutionTier
- New property:
- public int PriorityPerProcess { get; set; }
- New property:
- Microsoft.SharePoint.Administration.SPWebApplication
- New properties:
- public int StorageMetricsProcessingDuration { get; set; }
- public uint MaxDiscussionBoardItemsForSiteDataFolderQuery { get; set; }
- public uint? UserDefinedWorkflowMaximumComplexity { get; set; }
- New methods:
- public SPDeletedSiteCollection GetDeletedSites()
- public SPDeletedSiteCollection GetDeletedSites(SPDeletedSiteQuery query)
- public SPDeletedSiteCollection GetDeletedSites(Guid siteId)
- public SPDeletedSiteCollection GetDeletedSites(string sitePath)
- public void MigrateUsers(IMigrateUserCallback callback)
- New properties:
- Microsoft.SharePoint.Administration.SPWebService
- New properties:
- public int ImagingDownloadSizeLimit { get; set; }
- public bool EnableHostHeaderSiteBasedSchemeSelection { get; set; }
- New properties:
- Microsoft.SharePoint.Administration.Claims.SPActiveDirectoryClaimProvider
- New method:
- protected override void FillDefaultLocalizedDisplayName(CultureInfo culture, out string localizedName)
- New method:
- Microsoft.SharePoint.Administration.Claims.SPAllUserClaimProvider
- New method:
- protected override void FillDefaultLocalizedDisplayName(CultureInfo culture, out string localizedName)
- New method:
- Microsoft.SharePoint.Administration.Claims.SPClaimHierarchyProvider
- New methods:
- protected override void FillDefaultLocalizedDisplayName(CultureInfo culture, out string localizedName)
- public string GetLocalizedDisplayName()
- New methods:
- Microsoft.SharePoint.Administration.Claims.SPClaimProvider
- New property:
- public virtual bool SupportsUserKey { get; }
- New methods:
- protected override void FillDefaultLocalizedDisplayName(CultureInfo culture, out string localizedName)
- public string GetLocalizedDisplayName()
- public SPClaim UserKeyForEntity(SPClaim entity)
- public virtual string GetClaimTypeForUserKey()
- protected virtual SPClaim GetUserKeyForEntity(SPClaim entity)
- New property:
- Microsoft.SharePoint.Administration.Claims.SPClaimProviderDefinition
- New property:
- public bool IsVisible { get; set; }
- New property:
- Microsoft.SharePoint.Administration.Claims.SPClaimProviderOperationOptions
- New enum value:
- OverrideVisibleFlag
- New enum value:
- Microsoft.SharePoint.Administration.Claims.SPFormsClaimProvider
- New method:
- protected override void FillDefaultLocalizedDisplayName(CultureInfo culture, out string localizedName)
- New method:
- Microsoft.SharePoint.Administration.Claims.SPSystemClaimProvider
- New method:
- protected override void FillDefaultLocalizedDisplayName(CultureInfo culture, out string localizedName)
- New method:
- Microsoft.SharePoint.BusinessData.Administration.LobSystem
- New static method:
- public static LobSystem MergeXml(string xml, out string[] errors, PackageContents packageContents, AdministrationMetadataCatalog metadataCatalog, string settingId)
- New static method:
- Microsoft.SharePoint.BusinessData.Administration.TypeDescriptor
- New static method:
- public static TypeDescriptor MergeXml(string xml, out string[] errors, PackageContents packageContents, Parameter parameter, TypeDescriptor parent, string settingId)
- New static method:
- Microsoft.SharePoint.BusinessData.SharedService.BdcServiceApplicationProxy
- New methods:
- public bool IsSystemTypeEnabled(SystemType systemType)
- public void EnableSystemType(SystemType systemType, bool value)
- New methods:
- Microsoft.SharePoint.JSGrid.GridSerializer
- New method:
- public void ApplyPostViewIncrementalInsertsAndDeletes(IEnumerable
changes, Func
- public void ApplyPostViewIncrementalInsertsAndDeletes(IEnumerable
- New method:
- Microsoft.SharePoint.JSGrid.HierarchyNode
- New property:
- public HierarchyNode Parent { get; set; }
- New property:
- Microsoft.SharePoint.Utilities.SPUtility
- New static method:
- public static Stream ExecuteCellStorageBinaryRequest(SPFile spfile, Stream request, bool coalesce, ref Guid partitionID, string userName, bool coauthVersioning, string etagMatching, bool fExpectNoFileExists, string contentChangeUnit, string clientFileID, string bypassSchemaID, long nLockType, string lockID, long nTimeout, bool createParentFolder, out string etagReturn, out bool allRequestSucceeded, out int coalesceHRESULT, out string coalesceErrorMessage, out bool containHotboxData, out bool haveOnlyDemotionChanges, ref int binaryReqCountQuota)
- New static method:
- Microsoft.SharePoint.WebPartPages.ListFormWebPart
- New method:
- public bool ShouldSerializeTemplateName()
- New method:
- New classes:
- Microsoft.SharePoint.Administration.SPDeletedSite
- Microsoft.SharePoint.Administration.SPDeletedSiteCollection
- Microsoft.SharePoint.Administration.SPDeletedSiteLookupInfo
- Microsoft.SharePoint.Administration.SPDeletedSiteQuery
- Microsoft.SharePoint.Administration.SPUsageUserCodeRequests
- Microsoft.SharePoint.Administration.SPUsageUserCodeRequestsEntry
- Microsoft.SharePoint.Administration.SPUsageUserCodeRequestsMonitoredData
- Microsoft.SharePoint.Administration.SPUsageUserCodeRequestsMonitoredDataEntry
- Microsoft.SharePoint.Administration.Health.SPHealthAnalysisRuleInstance
- Microsoft.SharePoint.WebControls.IEVersionMetaTag
- New enum types:
- Microsoft.SharePoint.Administration.SPIdentifierType
- New interfaces:
- Microsoft.SharePoint.Administration.IMigrateUserCallback
- Microsoft.SharePoint.Administration.ISPSiteLookupProviderRecycleBin
- Microsoft.SharePoint.SPRecycleBinItemType
- Microsoft.SharePoint.Publishing.dll
- Microsoft.SharePoint.Publishing.Internal.CodeBehind
- New property:
- protected bool IsCurrentUserSiteAdmin { get; }
- New property:
- Microsoft.SharePoint.Publishing.WebControls.SpellCheckV4Action
- New method:
- protected bool ShouldRenderWithoutTabs()
- New method:
- Microsoft.SharePoint.Publishing.WebControls.EditingMenuActions.ConsoleAction
- New method:
- protected bool ShouldRenderWithoutTabs()
- New method:
- Microsoft.SharePoint.Publishing.Internal.CodeBehind
- Microsoft.SharePoint.Taxonomy.dll
- Microsoft.SharePoint.Taxonomy.TermStore
- New method:
- public Group GetSiteCollectionGroup(SPSite currentSite)
- New method:
- Microsoft.SharePoint.Taxonomy.TermStore
- Microsoft.SharePoint.Portal.dll
- Microsoft.Office.Server.UserProfiles.UserProfileService
- New methods:
- public void AddLeader(string accountName)
- public Leader[] GetLeaders()
- public void RemoveLeader(string accountName)
- New methods:
- Microsoft.Office.Server.UserProfiles.UserProfileService
- Microsoft.Office.Server.UserProfiles.dll
- Microsoft.Office.Server.SocialData.PluggableSocialSecurityTrimmerManager
- New methods:
- public static string[] GetUrlFoldersRequiringTrim(SPServiceContext serviceContext)
- public static string[] GetUrlFoldersToAlwaysAllow(SPServiceContext serviceContext)
- public static void SetTrimmerSettings(SPServiceContext serviceContext, bool enableTrimming)
- public static void SetTrimmerSettings(SPServiceContext serviceContext, string[] urlFoldersRequiringTrim, string[] urlFoldersToAlwaysAllow)
- New methods:
- Microsoft.Office.Server.UserProfiles.BusinessDataCatalogConnection
- New method:
- public void Delete()
- New method:
- Microsoft.Office.Server.UserProfiles.ConnectionManager
- New method:
- public DirectoryServiceConnection AddActiveDirectoryConnection(ConnectionType type, string displayName, string server, bool useSSL, string accountDomain, string accountUsername, SecureString accountPassword, List<DirectoryServiceNamingContext> namingContexts, string spsClaimProviderTypeValue, string spsClaimProviderIdValue, string adClaimIDMapAttribute)
- New method:
- Microsoft.Office.Server.UserProfiles.UserProfileManager
- New methods:
- public void AddLeader(string accountName)
- public Leader[] GetLeaders()
- public void RemoveLeader(string accountName)
- New methods:
- New classes:
- Microsoft.Office.Server.UserProfiles.Leader
- Microsoft.Office.Server.SocialData.PluggableSocialSecurityTrimmerManager
- Microsoft.Office.Server.Search.dll
- Microsoft.Office.Server.Search.Administration.CrawlTopologyState
- New enum values:
- ActiveToBeRemoved
- DeactivatingToBeRemoved
- New enum values:
- Microsoft.Office.Server.Search.Administration.SearchServiceApplication
- New property:
- public uint CrawlLogCleanupIntervalInDays { get; set; }
- New property:
- Microsoft.Office.Server.Search.Administration.SearchServiceApplicationProxy
- New property:
- public LocationConfigurationCollection LocationConfigurations { get; }
- New property:
- Microsoft.Office.Server.Search.Query.QueryInfo
- New property:
- public string CorrelationId { get; set; }
- New property:
- Microsoft.Office.Server.Search.Query.QueryManager
- New method:
- public System.Xml.XmlDocument GetResults()
- New method:
- Microsoft.Office.Server.Search.Administration.CrawlTopologyState
- Microsoft.SharePoint.PowerShell.dll
- New classes:
- Microsoft.SharePoint.PowerShell.SPDeletedSitePipeBind
- Microsoft.SharePoint.PowerShell.SPHealthAnalysisRuleInstancePipeBind
- New classes:
Service Accounts and Managed Service Accounts in SharePoint 2010
With SharePoint 2010 we now have the ability to allow SharePoint to manage various service accounts thus foregoing the need to have IT administrators manually manage password changes. This new feature is a great benefit to SharePoint administrators and security conscious admins in general as it allows us to easily enforce our corporate security policies by changing these passwords on a schedule, and the administrators don’t even know what the password is so the likelihood of a compromise due to a disgruntled admin, though not eliminated, is somewhat reduced.
But the introduction of this new feature isn’t all good. The complication comes from the fact that SharePoint 2010 doesn’t implement this capability consistently. So an account that is configured as a Managed Service Account and set to have its password changed automatically could also be used in certain places that don’t understand the managed account concept. When the managed account password is changed the feature that uses that account and only knows the username and password (so it does not use the managed account details) will effectively be broken. As an example, if you configure the Enterprise Search Service to use a managed account whose password is scheduled to be changed every 30 days and you use that same account for the content crawl account then when that password is changed the content crawl will cease to function as it will be unable to authenticate the account. It’s important to note, however, that this issue only comes to light when you configure the managed account to have it’s password changed automatically.
So what things can be managed accounts and what cannot? The following lists what I’ve come across so far (if I’ve missed anything please leave a comment so I can update these lists):
Managed Service Accounts:
- All Service Application Pool Accounts
- Access Service Application
- BCS Service Application
- Excel Services Service Application
- Metadata Service Application
- PerformancePoint Service Application
- Enterprise Search Service Application
- Secure Store Service Application
- Subscription Settings Service Application
- User Profile Service Application
- Visio Services Service Application
- Web Analytics Service Application
- Word Automation Service Application
- Word Viewing Service Application
- PowerPoint Viewing Service Application
- Security Token Service Application
- All Content Web Application Pools
- Service Instances
- Claims to Windows Token Service
- Document Conversion Launcher Service
- Document Conversion Load Balancer Service
- Microsoft SharePoint Foundation Sandboxed Code Service
- SharePoint Foundation Help Search
- SharePoint Server Search (Enterprise Search)
- Web Analytics Data Processing Service
Service Accounts (should not be managed):
- Search Crawl Accounts
- For Foundation Search and Server (Enterprise) Search
- Unattended User Accounts
- Excel Services Service Application
- Visio Services Service Application
- PerformancePoint Service Application
- (in general, any Secure Store application credentials)
- Object Cache Portal Accounts
- Super User Account
- Super Reader Account
- User Profile
- Synchronization Service Account (listed incorrectly on the FarmCredentialManagement.aspx page)
- Synchronization Connection Account
- Server Search Custom Crawl Rule Accounts
- Any crawl rule that specifies an account other than the default crawl account
Again, these are just the accounts that I’ve personally bumped up against so it may not be a complete listing.
Viewing and Creating Managed Accounts
To see the current list of Managed Service Accounts using Central Admin go to Security –> Configure managed accounts:
You can edit the settings for any managed account by simply clicking the edit icon associated with the account you wish to modify. Once on the Manage Account screen you can configure the automatic password change settings:
To perform the same tasks using Windows PowerShell we can use the Get-SPManagedAccount cmdlet to retrieve the list of managed accounts:

Or we can retrieve a specific account using the -Identity parameter or by passing in a Web Application or Service:

clTo change the settings for a Managed Account we can use the Set-SPManagedAccount cmdlet:
To create a new Managed Account we use the New-SPManagedAccount cmdlet. In the example below I’m manually creating a PSCredential object so that I can specify my password (pa$$w0rd) in script (very useful for building out dev or test environments – otherwise you should use Get-Credential to prompt for the password so that it is not hard coded anywhere):

Applying Managed Accounts
Once you have your Managed Accounts created you can begin to use them for things such as Service Instances and Service and Content Application Pools. To associate a managed account with a specific Service Instance using Central Admin you can go to Security –> Configure service accounts. On the Service Accounts page you can set the account used for the Farm Account, Service Instances, Web Content Application Pools, and Service Application Pools. The Service Instances are highlighted in the following image:
Service Instances
To set the account associated with a particular Service Instance using Windows PowerShell we simply get the ProcessIdentity property of the Service Instance and set its Username property. Once set we call Update() to update the Configuration Database and then Deploy() to push the change out to all Service Instances. To make this easier I put this code in a function that I can call by passing in the Service Instance and credentials to use:
function Set-ServiceIdentity($svc, $username)
{
$pi = $svc.Service.ProcessIdentity
if ($pi.Username-ne $username) {
$pi.Username= $username
$pi.Update()
$pi.Deploy()
}
}
Here’s an example of how you can call this function:

Service Application Pools
To create a new Service Application pool we use the New-SPServiceApplicationPool cmdlet and pass in the name of the Application Pool to create and the Managed Account to assign as the Application Pool identity:

It’s extremely important to note that the application pool that you create using the New-ServiceApplicationPool cmdlet cannot be used for your content Web Applications. Unfortunately there is no out-of-the-box equivalent for creating Application Pools for Web Applications.
Web Application Pools
As previously noted there is no cmdlet for creating Application Pools for Web Applications. Instead what you need to do is first check if the Application Pool you need already exists by using the SPWebService’s ContentService static property. If it exists then pass in just the name of the Application Pool to the New-SPWebApplication cmdlet, otherwise pass in the name and the Managed Account to use as the Application Pool’s identity:

Applying Service Accounts
When it comes to applying non-managed accounts to the various features things get a little more complicated. Let’s start with the Crawl Accounts.
SharePoint Foundation Search Service
For SharePoint Foundation Search we can set the crawl account (or content access account) using Central Admin by navigating to the Services on Server page and clicking the SharePoint Foundation [Help] Search link which takes you to the settings page where we can set the crawl account:
To set the same information using Windows PowerShell we actually have to go old-school and use STSADM as there’s no PowerShell equivalent cmdlet. Here’s a snippet of PowerShell code that I use to accomplish this:
function ConvertTo-UnsecureString([System.Security.SecureString]$string) { $unmanagedString = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($string) $unsecureString = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($unmanagedString) [System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($unmanagedString) return $unsecureString } $searchSvcAccount = Get-Credential "localdev\spsearchsvc" $crawlAccount = Get-Credential "localdev\spcrawl" $stsadmArgs = "-o spsearch -action start " + ` "-farmserviceaccount `"$($searchSvcAccount.Username)`" " + ` "-farmservicepassword `"$(ConvertTo-UnsecureString $searchSvcAccount.Password)`" " + ` "-farmcontentaccessaccount `"$($crawlAccount.Username)`" " + ` "-farmcontentaccesspassword `"$(ConvertTo-UnsecureString $crawlAccount.Password)`" " + ` "-databaseserver `"spsql1`" " + ` "-databasename `"SharePoint_FoundationSearch`"" Write-Host "Running: stsadm $stsadmArgs" $stsadmoutput = cmd /c "stsadm $stsadmArgs" 2>&1 if ($lastexitcode -ne 0) { throw "Unable to start Foundation Search Service.`n$stsadmoutput" }
Note that I’m using a helper function to convert the secure password to a static string which I can then pass to the STSADM spsearch command.
SharePoint Server Search Service
To manage the crawl account for the SharePoint Server Search Service (also known as the Enterprise Search Service) using Central Admin we simply need to navigate to the Search Administration page of the Service Application that we wish to modify and click the link for the Default content access account. This will bring up the following screen:
Note that by default this account will be set to be the same account you used for the Search Service Instance which is a Managed Account. If you do not change this account and you have configured SharePoint to manage the account password then your crawls will fail when the password changes. To make this change using Windows PowerShell we use the Set-SPEnterpriseSearchServiceApplication cmdlet:
$crawlAccount = Get-Credential "localdev\spcrawl"
$searchApp | Set-SPEnterpriseSearchServiceApplication -DefaultContentAccessAccountPassword $crawlAccount.Password -DefaultContentAccessAccountName $crawlAccount.Username
Remember not to do this step until after you have provisioned the Administration Component.
Object Cache Accounts
Many administrators when they first configure SharePoint 2010 and hit a Web Application for the first time are likely to see a recurring event in the event log stating that the object cache has not been configured correctly. The specific error is as follows:
Object Cache: The super user account utilized by the cache is not configured. This can increase the number of cache misses, which causes the page requests to consume unneccesary system resources.
This is essentially telling you that you have missed a manual configuration step in which you need to run some PowerShell to set two accounts for SharePoint to use to access the object cache:
function Set-WebAppUserPolicy($webApp, $userName, $userDisplayName, $perm) { [Microsoft.SharePoint.Administration.SPPolicyCollection]$policies = $webApp.Policies [Microsoft.SharePoint.Administration.SPPolicy]$policy = $policies.Add($userName, $userDisplayName) [Microsoft.SharePoint.Administration.SPPolicyRole]$policyRole = $webApp.PolicyRoles | where {$_.Name -eq $perm} if ($policyRole -ne $null) { $policy.PolicyRoleBindings.Add($policyRole) } $webApp.Update() } $webApp = Get-SPWebApplication "http://content" $portalSuperUserAccount = Get-Credential "localdev\SPSuperUser" $webApp.Properties["portalsuperuseraccount"] = $portalSuperUserAccount.UserName Set-WebAppUserPolicy $webApp $portalSuperUserAccount.UserName $portalSuperUserAccount.UserName "Full Control" $portalSuperReaderAccount = Get-Credential "localdev\SPSuperReader" $webApp.Properties["portalsuperreaderaccount"] = $portalSuperReaderAccount.UserName Set-WebAppUserPolicy $webApp $portalSuperReaderAccount.UserName $portalSuperReaderAccount.UserName "Full Read"
Make sure that you do not use the same account for both the super user and super reader. (And of course make sure you change the URL and account names to match your environment). For more information about these settings see the following TechNet article: http://technet.microsoft.com/en-us/library/ff758656.aspx
Unattended Accounts
There are some services, specifically the Visio Services Service Application, the Excel Services Service Application, and the PerformancePoint Service Application, that allow us to set an account that we can use for access data sources behind the scenes. These are called unattended access accounts. To set these accounts we must create a new target application in the Secure Store Service Application and associate the target application’s ID with the appropriate Service Application. The following PowerShell code demonstrates how to do this for the Visio Services Service Application (the Excel Services Service Application is virtually identical and just uses cmdlets specific to Excel rather than Visio; PerformancePoint is a lot simpler):
#Get the Visio Service App $svcApp = Get-SPServiceApplication | where {$_.TypeName -like "*Visio*"} #Get the existing unattended account app ID $unattendedServiceAccountApplicationID = ($svcApp | Get-SPVisioExternalData).UnattendedServiceAccountApplicationID #If the account isn't already set then set it if ([string]::IsNullOrEmpty($unattendedServiceAccountApplicationID)) { #Get our credentials $unattendedAccount = Get-Credential "localdev\SPUnattended" #Set the Target App Name and create the Target App $name = "$($svcApp.ID)-VisioUnattendedAccount" Write-Host "Creating Secure Store Target Application $name..." $secureStoreTargetApp = New-SPSecureStoreTargetApplication -Name $name ` -FriendlyName "Visio Services Unattended Account Target App" ` -ApplicationType Group ` -TimeoutInMinutes 3 #Set the group claim and admin principals $groupClaim = New-SPClaimsPrincipal -Identity "nt authority\authenticated users" -IdentityType WindowsSamAccountName $adminPrincipal = New-SPClaimsPrincipal -Identity "$($env:userdomain)\$($env:username)" -IdentityType WindowsSamAccountName #Set the account fields $usernameField = New-SPSecureStoreApplicationField -Name "User Name" -Type WindowsUserName -Masked:$false $passwordField = New-SPSecureStoreApplicationField -Name "Password" -Type WindowsPassword -Masked:$false $fields = $usernameField, $passwordField #Set the field values $secureUserName = ConvertTo-SecureString $unattendedAccount.UserName -AsPlainText -Force $securePassword = $unattendedAccount.Password $credentialValues = $secureUserName, $securePassword #Get the service context $subId = [Microsoft.SharePoint.SPSiteSubscriptionIdentifier]::Default $context = [Microsoft.SharePoint.SPServiceContext]::GetContext($svcApp.ServiceApplicationProxyGroup, $subId) #Check to see if the Secure Store App already exists $secureStoreApp = Get-SPSecureStoreApplication -ServiceContext $context -Name $name -ErrorAction SilentlyContinue if ($secureStoreApp -eq $null) { #Doesn't exist so create. Write-Host "Creating Secure Store Application..." $secureStoreApp = New-SPSecureStoreApplication -ServiceContext $context ` -TargetApplication $secureStoreTargetApp ` -Administrator $adminPrincipal ` -CredentialsOwnerGroup $groupClaim ` -Fields $fields } #Update the field values Write-Host "Updating Secure Store Group Credential Mapping..." Update-SPSecureStoreGroupCredentialMapping -Identity $secureStoreApp -Values $credentialValues #Set the unattended service account application ID $svcApp | Set-SPVisioExternalData -UnattendedServiceAccountApplicationID $name }
When it comes to PerformancePoint we have a lot less work we need to do as the product team was nice enough to make it so that the Set-SPPerformancePointSecureDataValues does all the work of setting up the target application for us (note though that they did screw up how the Service Application is passed into the cmdlet requiring you to pass in the ID of the Service Application rather than the actual Service Application object):
$unattendedAccount = Get-Credential "localdev\SPUnattended" $secureValues = Get-SPPerformancePointSecureDataValues -ServiceApplication $svcApp.Id if ($secureValues.DataSourceUnattendedServiceAccount -ne $unattendedServiceAccount.UserName) { Write-Host "Setting unattended service account $($unattendedServiceAccount.UserName)..." $svcApp.Id | Set-SPPerformancePointSecureDataValues -DataSourceUnattendedServiceAccount $unattendedServiceAccount }
User Profile Synchronization Service Identity
One thing to watch out for is when setting the account for the User Profile Synchronization Service. This service wants you to use the Farm Account as the identity. This means that your Farm Admin account cannot have it’s password managed by SharePoint if you intend to use this service (or at least, it shouldn’t be unless you don’t mind manually fixing this service every time your password changes – good luck with that BTW). Your Farm Admin account will always be a Managed Account (you can’t change that) so be extra careful when changing this accounts password (either manually or automatically). To set this account using Central Admin you can click Start next to the User Profile Synchronization Service entry on the Services on Server page.
To accomplish the same thing using PowerShell we need to get an instance of the Synchronization Service and set a few properties and call the SetSynchronizationMachine method passing in the username and password of the Farm Admin account (note that it requires the password be passed in as a standard string and not a secure string so I use my previously defined ConvertTo-UnsecureString function):
$syncMachine = Get-SPServer "sp2010dev" $profApp = Get-SPServiceApplication | where {$_.Name -eq "User Profile Service Application 1"} $account = Get-Credential "localdev\spfarm" if ($syncMachine.Address -eq $env:ComputerName) { $syncSvc = Get-SPServiceInstance -Server $env:ComputerName | where {$_.TypeName -eq "User Profile Synchronization Service"} $syncSvc.Status = [Microsoft.SharePoint.Administration.SPObjectStatus]::Provisioning $syncSvc.IsProvisioned = $false $syncSvc.UserProfileApplicationGuid = $profApp.Id $syncSvc.Update() $profApp.SetSynchronizationMachine($syncMachine.Address, $syncSvc.Id, $account.UserName, (ConvertTo-UnsecureString $account.Password)) } if ($syncSvc.Status -ne "Online") { Write-Host "Starting User Profile Synchronization Service..." Start-SPServiceInstance $syncSvc } do {Start-Sleep 2} while ((Get-SPServiceInstance -Server $env:ComputerName | where {$_.TypeName -eq "User Profile Synchronization Service"}).Status -ne "Online")
Summary
As you can see setting the accounts that are used throughout SharePoint 2010 is anything but consistent and in some cases a real pain in the a$$. I know I didn’t cover how to set every account (custom crawl rule accounts, user profile sync connection accounts, others?) but hopefully someone out there has already documented these, or if not perhaps they’d be nice enough to post a comment here for others benefit from (maybe one day I’ll add them myself but for now I think this post is quite long enough). As always, please let me know if I’ve missed something or otherwise got something wrong as I certainly don’t claim to have all the answers.
Happy PowerShelling ![]()
Getting an Inventory of All SharePoint Documents Using Windows PowerShell
I got an email today asking if I had anything that would generate a report detailing all the documents throughout an entire SharePoint Farm. As this wasn’t the first time I’ve been asked this same question I decided that I’d just go ahead and post the script for generating such a report.
The script is really quite straightforward – it simply iterates through all Web Applications, Site Collections, Webs, Lists, and finally, List Items. I skip any List that is not a Document Library (as well as the Central Admin site) and then build a hash table containing all the data I want to capture. I then convert that hash table to an object which is written to the pipeline.
All of this is placed in a function which I can call and then pipe the output to something like the Out-GridView cmdlet or the Export-Csv cmdlet. I also wrote the script so that it works with either SharePoint 2007 or SharePoint 2010 so that I don’t have to maintain two versions (I could have used cmdlets such as Get-SPWebApplication, Get-SPSite, and Get-SPWeb but there was little benefit to doing so and the script would be limited to SharePoint 2010).
One word of caution – in a large Farm this script should be run off hours or at least on a back facing server (not your WFE) – it’s going to generate a lot of traffic to your database.
function Get-DocInventory() { [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") $farm = [Microsoft.SharePoint.Administration.SPFarm]::Local foreach ($spService in $farm.Services) { if (!($spService -is [Microsoft.SharePoint.Administration.SPWebService])) { continue; } foreach ($webApp in $spService.WebApplications) { if ($webApp -is [Microsoft.SharePoint.Administration.SPAdministrationWebApplication]) { continue } foreach ($site in $webApp.Sites) { foreach ($web in $site.AllWebs) { foreach ($list in $web.Lists) { if ($list.BaseType -ne "DocumentLibrary") { continue } foreach ($item in $list.Items) { $data = @{ "Web Application" = $webApp.ToString() "Site" = $site.Url "Web" = $web.Url "list" = $list.Title "Item ID" = $item.ID "Item URL" = $item.Url "Item Title" = $item.Title "Item Created" = $item["Created"] "Item Modified" = $item["Modified"] "File Size" = $item.File.Length/1KB } New-Object PSObject -Property $data } } $web.Dispose(); } $site.Dispose() } } } } Get-DocInventory | Out-GridView #Get-DocInventory | Export-Csv -NoTypeInformation -Path c:\inventory.csv
Deploying SharePoint 2010 Solution Packages Using PowerShell
Update 4/19/2011: I've reworked this script completely. You can find the update here: http://blog.falchionconsulting.com/index.php/2011/04/deploying-sharepoint-2010-solution-package-using-powershell-revisited/
With SharePoint 2010 we can now deploy our Solution Packages using PowerShell. What’s cool about this is that it’s a bit easier than it was with 2007 to check if a package is already deployed and conditionally retract, delete, and then re-add and re-deploy. By now most people already know how to do this as it’s fairly straightforward but I thought I’d go ahead and share the script that I use as it’s great for deploying lots of Solution Packages to my various client environments in bulk.
Like most of my scripts this one is driven by an XML file but I have a core function which can be called directly – I just wrap that in another function which can iterate through the XML file thus facilitating bulk installs of packages. First lets look at the XML file:
<Solutions>
<Solution Path="W:\my.sharepoint.package.wsp" CASPolicies="false" GACDeployment="true">
<WebApplications>
<WebApplication>http://portal</WebApplication>
</WebApplications>
</Solution>
</Solutions>
As you can see the structure is fairly simplistic – just provide the path to the WSP file and whether it contains CAS policies and whether it should be deployed to the GAC or not. If it’s a Farm level solution (no web application resources) then simply omit the <WebApplications /> element. If you have more than one solution just add another <Solution /> element. If you’re deploying to multiple web applications then add as many <WebApplication /> elements as is needed.
Now we’ll take a look at the wrapper function which loops through the XML:
function Install-Solutions([string]$configFile) { if ([string]::IsNullOrEmpty($configFile)) { return } [xml]$solutionsConfig = Get-Content $configFile if ($solutionsConfig -eq $null) { return } $solutionsConfig.Solutions.Solution | ForEach-Object { [string]$path = $_.Path [bool]$gac = [bool]::Parse($_.GACDeployment) [bool]$cas = [bool]::Parse($_.CASPolicies) $webApps = $_.WebApplications.WebApplication Install-Solution $path $gac $cas $webApps } }
As you can see the code just loads the passed in file as an XmlDocument object and grabs each Solution element ($solutionsConfig.Solutions.Solution) and then iterates through each object using the ForEach-Object cmdlet. For pure convenience I grab each attribute and assign it to a local variable. And finally I call the Install-Solution function which is shown below:
function Install-Solution([string]$path, [bool]$gac, [bool]$cas, [string[]]$webApps = @()) { $spAdminServiceName = "SPAdminV4" [string]$name = Split-Path -Path $path -Leaf $solution = Get-SPSolution $name -ErrorAction SilentlyContinue if ($solution -ne $null) { #Retract the solution if ($solution.Deployed) { Write-Host "Retracting solution $name..." if ($solution.ContainsWebApplicationResource) { $solution | Uninstall-SPSolution -AllWebApplications -Confirm:$false } else { $solution | Uninstall-SPSolution -Confirm:$false } Stop-Service -Name $spAdminServiceName Start-SPAdminJob -Verbose Start-Service -Name $spAdminServiceName #Block until we're sure the solution is no longer deployed. do { Start-Sleep 2 } while ((Get-SPSolution $name).Deployed) } #Delete the solution Write-Host "Removing solution $name..." Get-SPSolution $name | Remove-SPSolution -Confirm:$false } #Add the solution Write-Host "Adding solution $name..." $solution = Add-SPSolution $path #Deploy the solution if (!$solution.ContainsWebApplicationResource) { Write-Host "Deploying solution $name to the Farm..." $solution | Install-SPSolution -GACDeployment:$gac -CASPolicies:$cas -Confirm:$false } else { if ($webApps -eq $null -or $webApps.Length -eq 0) { Write-Warning "The solution $name contains web application resources but no web applications were specified to deploy to." return } $webApps | ForEach-Object { Write-Host "Deploying solution $name to $_..." $solution | Install-SPSolution -GACDeployment:$gac -CASPolicies:$cas -WebApplication $_ -Confirm:$false } } Stop-Service -Name $spAdminServiceName Start-SPAdminJob -Verbose Start-Service -Name $spAdminServiceName #Block until we're sure the solution is deployed. do { Start-Sleep 2 } while (!((Get-SPSolution $name).Deployed)) }
The code looks more complicated than it really is. I first start out by getting the solution name which I always assume to be the filename of the WSP file. I then use that to get the SPSolution object using Get-SPSolution. If a value comes back then I check if it has been deployed and if it has then I retract it by calling Uninstall-SPSolution. The trick is knowing whether it has web application scoped resources and if it does then we need to retract using the -AllWebApplications parameter. Once retracted I stop the SharePoint Administration Service (SPAdminV4) so that I can call Start-SPAdminJob and force the retraction timer job to execute. Once the Start-SPAdminJob cmdlet returns I then restart the SharePoint Administration Service. With the solution retracted I can now delete the solution from the solution store using Remove-SPSolution (I re-get the solution to make sure that I get no errors due to the current variables state being invalid).
Once deleted I can then add the new solution to the store using the path provided (Add-SPSolution). Now that it’s in the store I can check if it has web application resources or not – if it does not then the deployment is simply a matter of calling Install-SPSolution and specifying whether it should be deployed to the GAC and if it contains CAS policies. If it does contain web application resources then I have to loop through all the web application items in the provided string array ($webApps) and then pass each one into a separate call to Install-SPSolution.
You now have two options for adding your solution: you can call the first function and pass in an XML file or you can call the second function directly. I’ll first show how to call the second function directly:
PS C:\>Install-Solution "w:\my.sharepoint.package.wsp" $true $false @("http://portal","http://mysites")
Now lets look at how to call the first function given an XML file named “solutions.xml” containing a structure similar to that shown above:
PS C:\>Install-Solutions "w:\solutions.xml"
Hopefully you’ll find this script useful for deploying your custom SharePoint 2010 Solution Packages.
SharePoint 2010 Service Application Charts
I, along with Paul Stork, recently gave a SharePoint 2010 deployment webcast where we discussed, among other things, Service Applications and some of the considerations that must be taken into account when planning your deployment strategy. We also presented a first look at SharePoint Composer and SharePoint Maestro, the two core products that ShareSquared has been developing for close to a year now.
During the presentation we mentioned that there were some great charts available to help you in planning your Service Applications but that they weren’t the easiest thing to find as they are buried in a series of technical diagrams on TechNet. You can find two of the three charts we referenced at this link, http://technet.microsoft.com/en-us/library/cc263199.aspx. I’ve also added another chart which shows the dependencies of one Service Application to another (note that this particular chart is a work in progress as we are still discovering odd dependency cases that only occur in certain situations).
Chart 1: Service Applications per SKU
The first chart identifies all the core Service Applications and whether they store data, can be used cross-farm, and to which SharePoint SKU they belong. This chart is particularly useful in planning your initial licensing requirements:
| Service applications | Description | Stores data? | Cross-farm? | SharePoint Foundation 2010 | SharePoint Server 2010 Standard | SharePoint Server 2010 Enterprise |
|---|---|---|---|---|---|---|
| Access Services | View, edit, and interact with Microsoft® Access® 2010 databases in a browser. | Cache | X | |||
| Business Data Connectivity | Access line-of-business (LOB) data systems. | DB | X | X | X | X |
| Excel Services Application | Viewing and interact with Excel files in a browser. | Cache | X | |||
| Managed Metadata Service | Access managed taxonomy hierarchies, keywords and social tagging infrastructure as well as Content Type publishing across site collections. | DB | X | X | X | |
| PerformancePoint | Provides the capabilities of PerformancePoint Services. | Cache | X | |||
| Search | Crawls content, produces index partitions, and serves search queries. | DB | X | X | X | |
| Secure Store Service | Provides single sign-on authentication to access multiple applications or services. | DB | X | X | X | |
| State Service | Provides temporary storage of user session data for SharePoint Server components. | DB | X | X | ||
| Usage and Health Data Collection | Collects farm wide usage and health data and provides the ability to view various usage and health reports. | DB | X | X | X | |
| User Profile | Adds support for My Sites, Profiles pages, Social Tagging and other social computing features. | DB | X | X | X | |
| Visio Graphics Service | Viewing and refresh of published Microsoft® Visio® diagrams in a Web browser. | Blob cache | X | |||
| Web Analytics | Provides Web Service interfaces. | X | X | X | ||
| Word Automation Services | Performs automated bulk document conversions. | Cache | X | X | ||
| Microsoft SharePoint Foundation Subscription Settings Service | Tracks subscription IDs and settings for services that are deployed in partitioned mode. Windows PowerShell only. | DB | X | X | X |
Source:
Visio (http://go.microsoft.com/fwlink/?LinkID=167090)
PDF (http://go.microsoft.com/fwlink/?LinkID=167092)
XPS (http://go.microsoft.com/fwlink/?LinkID=167091)
Chart 2: Databases That Support SharePoint 2010 Products
This next chart takes what was in diagram form in the original TechNet diagram and displays it in a chart so that it’s a bit easier to read. Use this chart when planning your SQL Server storage requirements:
| Service Application Database | Database | Relative Size | Size Guidance |
|---|---|---|---|
| Usage and Health Data Collection Service Application | Usage | Extra-large | Scale up. Only one database service application per farm. Place on separate spindle. |
| Business Data Connectivity Service Application | Business Data Connectivity | Small | Scale up. |
| Application Registry Service Application | Application Registry (used during upgrade only) | Small | Scale up. |
| Microsoft SharePoint Foundation Subscription Settings Service | Subscription Settings | Small | Scale up. You can scale out by creating additional service applications. |
| Search Service Application | Search Administration | Medium | Scale up. You can scale out by creating additional service applications. |
| Search Service Application | Crawl | Extra-large | Scale out. For large environments, put on a server that does not contain the Property databases. |
| Search Service Application | Property | Large to Extra-large | Scale out. For large environments, put on its own server for faster query results. |
| Web Analytics Service Application | Reporting | Extra-large | Scale up. |
| Web Analytics Service Application | Staging | Medium | Scale out. |
| State Service Application, Visio Service Application, InfoPath Forms Services | State | Medium-large | Scale out. |
| User Profile Service Application | Profile | Medium-large | Scale up. You can scale out by creating additional service applications. |
| User Profile Service Application | Synchronization | Medium-large | Scale up. You can scale out by creating additional service applications. |
| User Profile Service Application | Social Tagging | Small to Extra-large | Scale up. You can scale out by creating additional service applications. |
| Managed Metadata Service Application | Managed Metadata | Medium | Scale up. You can scale out by creating additional service applications. |
| Secure Store Service Application | Secure Store | Small | Scale up. You can scale out by creating additional service applications. |
| Word Automation Service Application | Word Automation Services | Small | Scale up. |
| PerformancePoint Service Application | PerformancePoint | Small | Scale up. You can scale out by creating additional service applications. |
Source:
Visio (http://go.microsoft.com/fwlink/?L
inkId=187970)PDF (http://go.microsoft.com/fwlink/?LinkId=187969)
XPS (http://go.microsoft.com/fwlink/?LinkId=187971)
Chart 3: Service Application Dependencies
This last chart is one that we’ve been manually constructing based on our experiences with automating the setup of Service Applications. When doing a scripted install (or even when you use the FCW or manually configure Service Applications) it’s critical to know which Service Applications are dependents for other Service Applications. For example, if you are configuring the User Profile Service Application you must also configure the Managed Metadata Service Application. If you don’t do this you will get errors stating that certain fields cannot be edited when editing a user’s profile – these errors don’t give any indication that what’s missing is the Managed Metadata Service Application – you just have know.
The following chart is an attempt to help users with this hurdle – note that it is still a work in progress as it is very difficult to detect all dependencies as some are only a dependency under certain usage scenarios. Anything with an asterisks (*) next to the “X” indicates that the dependency is conditional based on usage scenarios:
| Service Applications |
| ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Access | X | ||||||||||||||
| Business Data Connectivity | X* | X* | |||||||||||||
| Excel Services | X* | ||||||||||||||
| Managed Metadata | X* | ||||||||||||||
| PerformancePoint | X* | ||||||||||||||
| Foundation Search | |||||||||||||||
| Enterprise Search | X* | X | X* | X* | |||||||||||
| Secure Store | X* | ||||||||||||||
| State | |||||||||||||||
| Usage and Health Data Collection | |||||||||||||||
| User Profile | X* | X* | X* | X* | |||||||||||
| Visio Graphics | X* | X | |||||||||||||
| Web Analytics | X | ||||||||||||||
| Word Automation | X* | ||||||||||||||
| Subscription Settings |
The way you read this chart is to find the Service Application of interest on the left and follow it to the right to see what Service Application it depends on. As you can see there’s not a lot of dependencies and most of the ones that do exist are conditional (for example, all the ones that depend on the Subscription Settings Service Application only depend on it if using Partitioning Mode, or basically a multi-tenant configuration).
As this chart is a work in progress I appreciate any feedback on it’s accuracy. If anyone notices anything that is incorrect with the chart please add a comment and I will be sure to update it accordingly.
Announcing My SharePoint 2010 PowerShell Cmdlets & STSADM Commands Now Available for Download
I’ve been wanting to release the SharePoint 2010 version of my STSADM extensions for quite some time but honestly just haven’t had the time to migrate as many as I would have liked. With over 145 STSADM extensions for SharePoint 2007 it was a challenge determining which ones I should focus on initially for the migration.
But today I’m happy to announce my initial release which contains 46 PowerShell cmdlets and 56 STSADM commands specific to SharePoint 2010. Yup, you read right, I’ve decided to maintain support for my STSADM commands and have been migrating them over as I create the equivalent replacement PowerShell cmdlet (though I recommend you don’t use them and suck it up and get used to PowerShell). You should note that there are more STSADM commands than PowerShell cmdlets – that’s because some of the things I was doing with STSADM can now easily be done with out of the box PowerShell cmdlets (I also have new PowerShell cmdlets that do not have an STSADM equivalent – everything new I create will be a cmdlet and I’ll create no new STSADM commands).
It’s going to take me a while to create all the posts needed to explain each cmdlet (assuming I create one at all) so for now I’ve created this simple table which lists all the STSADM commands and PowerShell cmdlets that are available in this initial release (I’ll eventually update my command index page but for now let this serve as the main reference for what is available as of 5/14/2010):
| STSADM Commands | PowerShell Cmdlets | Notes |
|---|---|---|
| gl-activatefeature | Enable-SPFeature2 | There’s an OOTB Enable-SPFeature cmdlet, this one simple adds some capabilities which were present in my existing STSADM command. |
| gl-addaudiencerule | New-SPAudienceRule | |
| gl-addavailablesitetemplate | I’ll eventually create a cmdlet for this. | |
| gl-adduser2 | Use the OOTB New-SPUser cmdlet. | |
| gl-adduserpolicyforwebapp | Add-SPWebApplicationUserPolicy | |
| gl-applytheme | This can be done pretty easily using Get-SPWeb and the ApplyTheme() method of the SPWeb object. | |
| gl-backup | Use the OOTB Backup-SPFarm and Backup-SPSite cmdlets. | |
| gl-backupsites | Backup-SPSite2 | Extends Backup-SPSite by including IIS settings. |
| gl-convertsubsitetositecollection | ConvertTo-SPSite | |
| gl-copycontenttypes | Copy-SPContentType | |
| gl-copylist | Copy-SPList | |
| gl-copylistsecurity | Copy-SPListSecurity | |
| gl-createaudience | New-SPAudience | |
| gl-createcontentdb | Use the OOTB New-SPContentDatabase cmdlet. | |
| gl-createpublishingpage | New-SPPublishingPage | |
| gl-createquotatemplate | New-SPQuotaTemplate | |
| gl-createwebapp | Use the OOTB New-SPWebApplication cmdlet | |
| gl-deactivatefeature | Disable-SPFeature2 | Extends the OOTB Disable-SPFeature cmdlet. |
| gl-deleteallusers | I probably won’t replicate this as it is easily done using the OOTB Remove-SPUser cmdlet. | |
| gl-deleteaudience | Remove-SPAudience | |
| gl-deletelist | Remove-SPList | |
| gl-deletewebapp | Use the OOTB Remove-SPWebApplication cmdlet. | |
| gl-disableuserpermissionforwebapp | This is fairly easy to do OOTB so I may not create a cmdlet for it. | |
| gl-editquotatemplate | Set-SPQuotaTemplate | |
| gl-enableuserpermissionforwebapp | This is fairly easy to do OOTB so I may not create a cmdlet for it. | |
| gl-enumaudiencerules | Export-SPAudienceRules | |
| gl-enumavailablepagelayouts | Get-SPPublishingPageLayout | |
| gl-enumavailablesitetemplates | Get-SPAvailableWebTemplates | |
| gl-enumeffectivebaseperms | This is fairly easy to do OOTB so I may not create a cmdlet for it. | |
| gl-enumfeatures | Use the OOTB Get-SPFeature cmdlet. | |
| gl-enuminstalledsitetemplates | Use the OOTB Get-SPWebTemplate cmdlet. | |
|
gl-enumpagewebparts | Get-SPWebPartList | |
| gl-enumunghostedfiles | Get-SPCustomizedPages | |
| gl-execadmsvcjobs | Start-SPAdminJob2 | I honestly need to research this a bit more as I’m not sure it’s necessary anymore but I’ve replicated the functionality in case someone finds it useful. |
| gl-exportaudiences | Export-SPAudiences | |
| gl-exportcontenttypes | Export-SPContentType | |
| gl-exportlist | Export-SPWeb2 | I’ve just extended the Export-SPWeb2 cmdlet to add additional parameters. |
| gl-exportlistsecurity | Export-SPListSecurity | |
| gl-extendwebapp | Use the OOTB New-SPWebApplicationExtension cmdlet. | |
| gl-fixpublishingpagespagelayouturl | Repair-SPPageLayoutUrl | |
| gl-importaudiences | Import-SPAudiences | |
| gl-importlist | Import-SPWeb2 | I’ve just extended the Import-SPWeb2 cmdlet to add additional parameters. |
| gl-importlistsecurity | Import-SPListSecurity | |
| gl-listaudiencetargeting | Set-SPListAudienceTargeting | |
| gl-managecontentdbsettings | Use the OOTB Set-SPContentDatabase cmdlet. | |
| gl-propagatecontenttype | Propagate-SPContentType | |
| gl-publishitems | Publish-SPListItems | |
| gl-reghostfile | Reset-SPCustomizedPages | |
| gl-removeavailablesitetemplate | I’ll eventually create a cmdlet for this (maybe). | |
| gl-repairsitecollectionimportedfromsubsite | Repair-SPSite | |
| gl-replacewebpartcontent | Replace-SPWebPartContent | |
| gl-setbackconnectionhostnames | Set-SPBackConnectionHostNames | |
| gl-setselfservicesitecreation | Not sure if I’ll migrate this or not. | |
| gl-syncquotas | Set-SPQuota | |
| gl-tracelog | Use the OOTB Set-SPDiagnosticConfig cmdlet. | |
| gl-unextendwebapp | Use the OOTB Remove-SPWebApplication cmdlet. | |
| Get-SPAudience | ||
| Get-SPAudienceManager | ||
| Get-SPContentType | ||
| Get-SPFile | ||
| Get-SPLimitedWebPartManager | ||
| Get-SPList | ||
| Get-SPPublishingPage | ||
| Get-SPQuotaTemplate | ||
| Set-SPAudience |
For those that know a thing or two about cmdlet development you might be interested in knowing that I am dynamically generating the help XML file for the cmdlets. If you download the source you’ll find a class which uses reflection to interrogate the assembly and dynamically build the help file just prior to building the WSP package. This saved me literally days of hand editing XML.
You can download the source and WSP files here or from the Downloads page:
- SharePoint 2010 PowerShell Cmdlets Source Code
- SharePoint 2010 Server Cmdlets WSP
- SharePoint 2010 Foundation Cmdlets WSP
After you deploy the package you can type “help <cmdlet name>” to get detailed help about each cmdlet, including parameter descriptions and example usage. If you want to see the list of cmdlets installed type the following:
gcm | where {$_.DLL –like "*lapointe*"}
As always, your use of these cmdlets/stsadm commands is at your own risk – I do as much testing as I can but every environment is different and there’s simply not enough time in a day. If you have any suggestions or feedback please don’t hesitate to leave a comment – I appreciate all of them!
Discovering Who Has Access to SharePoint 2010 Securable Objects
I've talked on several occasions about how we can easily use the SharePoint 2010 object model (OM) to discover who has access to a securable object (SPWeb, SPList, or SPListItem) and the fact that we can use the same mechanisms within PowerShell to create useful security/audit reports. On some of those occasions I've shown a version of a PowerShell script which gives you a dump to the screen or a text file of every securable object and who has access to it and how they were given access to it - today I'd like to share a new version of that script.
Before we get to the actual script let's first talk about how to get the information. All securable objects have a method named GetUserEffectivePermissionInfo which is defined in the abstract base class SPSecurableObject (in 2007 this method was defined directly on the SPWeb, SPList, and SPListItem objects). This method returns back an SPPermissionInfo object which we can use to inspect the various role definition bindings and corresponding permission levels.
Once we have the permission details we simple loop through the SPRoleAssignments objects via the RoleAssignments property. This will give us information about how the user is given access to the resource. Next we look at the RoleDefinitionBindings property which returns back a collection of SPRoleDefinition objects that tell us about the type of access granted (e.g., Full Control, etc.).
I then take all this information, stick it in a hash table which I then use to create a new object which gets written to the pipeline.
So with that, let's take a look at the code:
function Get-SPUserEffectivePermissions( [object[]]$users, [Microsoft.SharePoint.SPSecurableObject]$InputObject) { begin { } process { $so = $InputObject if ($so -eq $null) { $so = $_ } if ($so -isnot [Microsoft.SharePoint.SPSecurableObject]) { throw "A valid SPWeb, SPList, or SPListItem must be provided." } foreach ($user in $users) { # Set the users login name $loginName = $user if ($user -is [Microsoft.SharePoint.SPUser] -or $user -is [PSCustomObject]) { $loginName = $user.LoginName } if ($loginName -eq $null) { throw "The provided user is null or empty. Specify a valid SPUser object or login name." } # Get the users permission details. $permInfo = $so.GetUserEffectivePermissionInfo($loginName) # Determine the URL to the securable object being evaluated $resource = $null if ($so -is [Microsoft.SharePoint.SPWeb]) { $resource = $so.Url } elseif ($so -is [Microsoft.SharePoint.SPList]) { $resource = $so.ParentWeb.Site.MakeFullUrl($so.RootFolder.ServerRelativeUrl) } elseif ($so -is [Microsoft.SharePoint.SPListItem]) { $resource = $so.ParentList.ParentWeb.Site.MakeFullUrl($so.Url) } # Get the role assignments and iterate through them $roleAssignments = $permInfo.RoleAssignments if ($roleAssignments.Count -gt 0) { foreach ($roleAssignment in $roleAssignments) { $member = $roleAssignment.Member # Build a string array of all the permission level names $permName = @() foreach ($definition in $roleAssignment.RoleDefinitionBindings) { $permName += $definition.Name } # Determine how the users permissions were assigned $assignment = "Direct Assignment" if ($member -is [Microsoft.SharePoint.SPGroup]) { $assignment = $member.Name } else { if ($member.IsDomainGroup -and ($member.LoginName -ne $loginName)) { $assignment = $member.LoginName } } # Create a hash table with all the data $hash = @{ Resource = $resource "Resource Type" = $so.GetType().Name User = $loginName Permission = $permName -join ", " "Granted By" = $assignment } # Convert the hash to an object and output to the pipeline New-Object PSObject -Property $hash } } } } end {} }
Great - we've got the code - so now you're probably asking, "how the heck do I use it?" Well the first thing you need to do is save it to a file, let's call it SecurityReport.ps1 and we'll put it in the root of the C drive. Once saved we can load it in memory using the following:
C:\ PS> . .\SecurityReport.ps1
Now for the fun stuff
. The examples I'm going to show will build off of each other and will eventually conclude with an example that gives me a report for all users and all securable objects throughout the entire farm. The first example I want to show is how to retrieve a report for a single user and a single web (we'll reuse the $user variable throughout the script so I'll only define it once here):
$user = "sp2010\siteowner2" Get-SPWeb http://portal | Get-SPUserEffectivePermissions $user | Out-GridView -Title "Web Permissions for $user"
Running this command will generate a grid view as shown here:

Note that I could have just as easily saved the results to a CSV file which I could then open in Excel using the Export-Csv cmdlet:
Get-SPWeb http://portal | Get-SPUserEffectivePermissions $user | Export-Csv -NoTypeInformation -Path c:\perms.csv
For this next example I'm going to show the permissions for the same user for ALL webs throughout the entire farm (note that this won't include lists or items):
Get-SPSite -Limit All | Get-SPWeb | Get-SPUserEffectivePermissions $user | Out-GridView -Title "All Web Permissions for $user"
Now I want to get the permissions for the same user for all lists throughout the entire farm:
Get-SPSite -Limit All | Get-SPWeb | %{$_.Lists | Get-SPUserEffectivePermissions $user} | Out-GridView -Title "List Permissions for $user"
Now we're going to get nice and deep and show the permissions for every single item throughout the entire farm (probably don't want to run this on any front-end servers):
Get-SPSite -Limit All | Get-SPWeb | %{$_.Lists | %{$_.Items | Get-SPUserEffectivePermissions $user}} | Out-GridView -Title "Item Permissions for $user"
So now that I've shown you how to get the individual securable objects results throughout the farm for a single user let's now go ahead and stitch them together into one report:
Get-SPSite -Limit All | ForEach-Object { $site = $_ $webPermissions += $site | Get-SPWeb –Limit All | Get-SPUserEffectivePermissions $user $listPermissions += $site | Get-SPWeb –Limit All | %{$_.Lists | Get-SPUserEffectivePermissions $user} $itemPermissions += $site | Get-SPWeb –Limit All | %{$_.Lists | %{$_.Items | Get-SPUserEffectivePermissions $user}} $site.Dispose(); } $webPermissions + $listPermissions + $itemPermissions | Out-GridView -Title "Web, List, and Item Permissions for $user"
In this example I'm simply performing the same calls but appending to an array of objects and then dumping the combination of those arrays to the grid. Note that in this case I'm calling $site.Dispose() but below I'll be using the SPAssignmentCollection to dispose of objects - keep reading for an explanation.
So now lets take it one step further and see how we can get the same reports but this time for every user. We'll start with webs again - in this example we'll get the permissions for all users for a given site:
$gc = Start-SPAssignment $site = $gc | Get-SPSite http://portal $site | Get-SPWeb –Limit All | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName) | Out-GridView -Title "Web Permissions for All Users In $($site.Url)" $gc | Stop-SPAssignment
As you can see I'm basically using the SiteUsers property from the root web and passing the login name for each user into the function. Note that here I'm using the Start-SPAssignment and Stop-SPAssignment cmdlets - that's because I'm using the SPSite object after the pipeline execution finishes (as opposed to the above) so I need to make sure it gets disposed (I could just as easily called Dispose on the object as I did above but I'm attempting to demonstrate when/why you'd use the assignment collections).
Now lets see the lists:
$gc = Start-SPAssignment $site = $gc | Get-SPSite http://portal $site | Get-SPWeb –Limit All | %{$_.Lists | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)} | Out-GridView -Title "List Permissions for All Users in $($site.Url)" $gc | Stop-SPAssignment
Starting to see a pattern? Let's take a look at the list items now:
$gc = Start-SPAssignment $site = $gc | Get-SPSite http://portal $site | Get-SPWeb –Limit All | %{$_.Lists | %{$_.Items | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)}} | Out-GridView -Title "Item Permissions for All Users in $($site.Url)" $gc | Stop-SPAssignment
Great! So now lets piece this last bit together so we can see the permissions for all webs, lists, and list items for every user within a single site collection:
$gc = Start-SPAssignment $site = $gc | Get-SPSite http://portal $webPermissions = $site | Get-SPWeb –Limit All | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName) $listPermissions = $site | Get-SPWeb –Limit All | %{$_.Lists | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)} $itemPermissions = $site | Get-SPWeb –Limit All | %{$_.Lists | %{$_.Items | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)}} $webPermissions + $listPermissions + $itemPermissions Out-GridView -Title "Web, List, and Item Permissions for All Users in $($site.Url)" $gc | Stop-SPAssignment
Alright, we're almost done - let's now stitch this all together and generate a single report showing all permissions for all securable objects (webs, lists, and list items) for every user within every site collection:
Get-SPSite -Limit All | ForEach-Object { $site = $_ $webPermissions += $site | Get-SPWeb –Limit All | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName) $listPermissions += $site | Get-SPWeb –Limit All | %{$_.Lists | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)} $itemPermissions += $site | Get-SPWeb –Limit All | %{$_.Lists | %{$_.Items | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)}} $site.Dispose(); } $webPermissions + $listPermissions + $itemPermissions | Out-GridView -Title "Web, List, and Item Permissions for All Users in All Sites"
Note in this last example, as I did previously when looping through all site collections, I'm calling the Dispose() method inside the ForEach-Object script block. I do this because objects wouldn't otherwise get disposed until the pipeline execution has finished and because it's continuing to iterate so the pipeline has not yet completed. If I used the assignment collection I wouldn't get a disposal until after I'm done iterating which would be too late - I want to dispose right when I'm done with the individual SPSite objects to avoid out of memory errors.
Reporting on who has access to what is one of the things I get asked about most frequently so hopefully this code sample and corresponding examples will prove to be useful to people. One possible area of improvement to the script would be to accommodate groups being passed in - right now I'm only considering users; and of course you could easily turn the example usages into functions. As always, if anyone has any feedback (bugs, improvements, etc.) please post here so that myself and others may benefit.