|
|
-
Background and Problem
Recently I had to reset the permission of a large number of files so that they would inherit permissions from the library instead of having their own customized permissions. I had gotten into a pickle after copying over 50 thousand files over to a new location only to realize later that the client did not need the file-level permission from the old location.
The Solution
Of course I could have redone the copy, which was originally with a third party tool, specifying not to include the permissions. I did that for the files I copied later. For the first set of file, I simply wrote a simple one-liner PowerShell to do the reinheritance as follows.
(get-spweb "http://example.org/sites/somesite").Lists["Collaboration"].Items | %{ $_.ResetRoleInheritance() }
I was surprised by how fast this runs. It only took a few seconds. It went ahead and reset all the items and folders in the library.
|
-
Background and Problem
Often before dereferencing variables or call methods, we need check for null so that we avoid throwing an exception. Sometimes it is desirable to throw an exception, have it logged, show to the user or sent to an administrator so that the issue can be addressed, but in any situation where it is known that a variable may correctly be null or if you are writing error handling code where all bets are off, you must explicitly check before you dereference. While handling an error condition in some Active Directory code recently I had to write this lovely snippit:
string userDistinguishedName = string.Empty;
string connectedServer = "Not determined";
if (userPrincipal != null)
userDistinguishedName = userPrincipal.DistinguishedName;
if (groupPrincipalContext != null)
connectedServer = groupPrincipalContext.ConnectedServer;
string errorText = string.Format(
@"User: '{0}'; Group: '{1}\{2}'; Group container: '{3}'; Group Context Server: '{4}'",
userDistinguishedName, groupDomainName, groupName,
commonContainer,
connectedServer);
From the point of view of code elegance, this snippit is not actually lovely at all. One issue is that it spreads out the relatively simply task of creating a text message out to several lines. String.Format handles null values for us very nicely, but it does not and C# does not give us an easy way to perform the "." (dot) dereference safely handling for nulls and punting in a way similar to COALESE in SQL. As a side note, C# has a convenient ?? (double question mark) operator for COALESE-like functionality but does not handle dereferencing. Another issue with the code above is that it also has an unnatural sequence of logic and concerns. This is a very common issue in programming, but as much as it can be avoided the better. In this case all we want to do is construct a simple string but to do so, we have to start preparing for it six lines earlier by declaring otherwise useless variables with default values, and using if..then logic, as if we were programming in assembler or something. Somebody pull out an 8086 so that I can do a JZ (jump if zero). What a pain!
The Solution
I wrote a simple extensions class that in C# will attach to any class and allow us to write much cleaner, more natural code.
static class Extensions
{
/// <summary>
/// Dereference only if the object is not null. May throw exception if
/// the property 'getter' method throws an exception, which can happen
/// depending on the internal state of the object. Use SafeIfNotNull
/// to eat any exceptions due to the call.
/// </summary>
/// <returns>The value returned by the 'getter' method, or the default
/// value for the TReturn type if the instance is null.</returns>
public static TReturn IfNotNull<t, treturn="">(
this T instance, Func<t, treturn=""> getter) where T : class
{
return IfNotNull<t, treturn="">(instance, getter,
default(TReturn));
}
/// <summary>
/// Dereference only if the object is not null. Will not throw
/// exception if the property 'getter' method throws an exception,
/// which can happen depending on the internal state of the object.
/// </summary>
/// <returns>The value returned by the 'getter' method, or the default
/// value for the TReturn type if either
/// the instance is null or if the getter throws an exception.</returns>
public static TReturn SafeIfNotNull<t, treturn="">(this T instance,
Func<t, treturn=""> getter) where T : class
{
return SafeIfNotNull<t, treturn="">(instance, getter,
default(TReturn));
}
/// <summary>
/// Dereference only if the object is not null. Will not throw exception
/// if the property 'getter' method throws an exception, which can
/// happen depending on the internal state of the object.</summary>
/// <returns>The value returned by the 'getter' method, or
/// <i>defaultValue</i> for the TReturn type if
/// the instance is null or if the getter throws an exception.
/// </returns>
public static TReturn SafeIfNotNull<t, treturn="">(this T instance,
Func<t, treturn=""> getter, TReturn defaultValue) where T : class
{
try {
return IfNotNull<t, treturn="">(
instance, getter, default(TReturn));
}
catch { return defaultValue; }
}
/// <summary>
/// Dereference only if the object is not null. May throw exception if
/// the property 'getter' method throws an exception, which can
/// happen depending on the internal state of the object. Use
/// SafeIfNotNull to eat any exceptions due to the call.</summary>
/// <returns>The value returned by the 'getter' method, or
/// <i>defaultValue</i> for the TReturn type if the instance is null.
/// </returns>
public static TReturn IfNotNull<t, treturn="">(this T instance,
Func<t, treturn=""> getter, TReturn defaultValue) where T : class
{
if (instance != null)
return getter(instance);
return defaultValue;
}
}
How to use itUsing this solution is intuitive if you have used lambda expressions in C# already. The following example shows the same error message construction as above, except this time using the IfNotNull method.
string errorText = string.Format(
@"User: '{0}'; Group: '{1}\{2}'; Group container: '{3}'; Group Context Server: '{4}'",
userPrincipal.IfNotNull(u => u.DistinguishedName), groupDomainName, groupName,
commonContainer,
groupPrincipalContext.IfNotNull(g => g.ConnectedServer, "Not determined"));
As you can see, the concerns are better encapsulated and kept together and the main objective of constructing an error string is the first thing you see following by the format we want followed by the values. Within those values we can handle the minor nuisance of having handle null values and provide defaults. I included extensive comments on the functions so you should be able to read those so that you understand how and when to use each one. Don't you wish all C# methods were so documented? Feedback I would appreciate any feedback and in particular, any improvements to the code. I did not allow it to handle methods or setter function for no other reason other than that I did not need it, but it would also be useful.
Please contact me directly at roberto.ortega (at) emc.com.
|
-
Background and Problem
Recently I had to check in a large number of files in that were checked out to me due to another process that I had run. SharePoint does provide a simple way to do this for many
files, but since I also had to run this over several document libraries.
The Solution
I wrote a simple solution to do the check in.
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") > $null
function global:Get-SPSite($url){
return new-Object Microsoft.SharePoint.SPSite($url)
}
#Change these variables to your site URL and list name
$siteColletion = Get-SPSite("http://example.org/");
$folder = $siteColletion.RootWeb.Folders["Documents"];
$collFiles = $folder.Files;
for ($intIndex=0; $intIndex -ne $folder.Count; $intIndex++)
{
if ($folder[$intIndex].CheckedOutBy.LoginName -eq "domain\ortegroadmin")
{
$folder[$intIndex].CheckIn("");
}
}
#Dispose of the site object
$siteColletion.Dispose()
I did not parametrize the script. Instead, I ran it a debugger to allow me to correct the myriad of issues that may come up while handling files in general. It would be interesting to also handle the case of taking ownership of checked out files and undoing them. This can be useful for running certain third-party tools over the document library if they can't handle checked out files well.
|
-
Background
Often when deploying or upgrading a SharePoint-based application, it is necessary to update the schema or content for the site. As an example if a simple web part is to be updated so that it supports a new field in a list, the following will typically need to take place: - The field needs to be added to the list or content type.
- The field order needs to be set for the list.
- The field may need to be surfaced in a view for display, filtering, grouping, or sorting.
- The field values for existing items may need to be set.
- The web part must be deployed and made available within the site.
- The web part must be added to a page and configured.
Code deployment best practice
In this situation and in any other where new functionality is being delivered in a solution, the most elegant solution is to handle this programmatically rather than by using manual steps. I recommend that the programmatic approach is the best practice. As is often the case, best practice is easier said than done. This approach will require using API that programmers generally do not use very much. Testing that the deployment code works can also be very difficult because for many code/run/test cycles you will need to perform a site collection restoration which can take several minutes or longer. The ideal final result would be a simple command-line or one-click installation. To achieve this, the programmers need to write all the deployment code. There are several ways to do this for SharePoint application deployment and upgrades and each has its own merits. - Create an elaborate batch file, or PowerShell scripts that automate the installation of the solution, by preparing the environment as in steps 1-4 above, deploy the web part file or WSP solution containing it, and add it to the page.
- Create a simple batch files or scripts for deployment of WSP and write deployment code within the activate and deactivate event receivers for the features within the WSP.
With either approach, once the code is well tested, you can free up valuable time that you would otherwise need to deploy and redeploy in each environment. You probably have a local development environment on your workstation or a virtual machine, a colleague may also have their own local dev environment, you should also have a central or integration development environment for combining everyone's code, a test environment and a production environment. Each of these will need to be deployed possibly multiple times, especially in the case of integration dev and test. If your deployment is not automated, then in each environment you may have installation errors that prevent you from reliably reproducing features and bugs across the environment as you promote code and find bugs.
The alternative, which is manual deployment and configuration, can certainly be made relatively rigorous by having documentation for the steps to take while deploying. The problem with this tends to be the lack of governance and the general distaste people have for following long detailed written instructions. In certain industries, such as pharmaceuticals and certain manufacturing, these written instructions for deployment are common. They have their basis in manufacturing operations and may be called IQ/OP/PQ for Installation Qualification, Operation Qualification, and Performance Qualification. They even come with built-in verification steps that must be individually signed off on and governance around the entire process such that the installer must submit the checklist supporting the fact that all steps were read and evaluated for correctness on the effected machines. This process is often the bane of operational personnel in IT. And writing it and getting approval from the governance team for the level of detail sufficient screen shots and sufficient testing procedures can be very time consuming. In this respect, if you have a system that deploys itself with a single command line, then the only thing that the installer has to do is verify that certain new deployed elements exist and that error logs and event logs do not show that any errors or warnings that are related to the installation.
|
-
Background and Problem
While preparing a development environment for myself, I ran into an error that took me some time to resolve. I had restored a copy of a site collection that contained a SharePoint-based application I had developed. This application used OOTB SharePoint forms, ASPX custom pages and also InfoPath forms. When I had everything set up, I began to test the application to make sure the configuration was okay. Everything was working except for the InfoPath forms.
The error shown to the user is “The specified form template could not be found, or is not compatible with rendering in the browser. It might need to be republished as a browser-enabled form.” The only verbose message that was logged to the ULS was “Failing to map invalid relative URL ~list/Item/template.xsn to an absolute site URL” and “FormInvocation: Could not find list ~list/Item/template.xsn.” Other than that were no other errors.
The Solution
As is often the case the solution was simple once I had figured it out. I had installed my development system using a standard license, while the production system was using an enterprise license. When I set the license I figured I would know when I have an issue that requires enterprise and I can upgrade to it if I need to. Since I was not getting an error that directly indicated that a feature was not available or that the license level was not high enough for the operation, I looked elsewhere to solve the problem for a couple of hours. After upgrading to enterprise, the InfoPath forms worked perfectly.
This experience shows that restoring a site collection from enterprise to standard can have very strange effects. I stumbled on this once, but there be many more. There is no warning when do the restore, and the error messages that surface later do not indicate the root of the problem. It’s even possible that some things break and do not produce an error message at all. In my example, the “error” logging is actually at the verbose level. Therefore it simply takes knowing that even for your development environment, you will need the same type of license as production no matter what features you think you need to use and that we cannot depend on getting an error message that will specifically tell us that a certain feature will not work due the license level.
Additional Notes
On my development machine, it appears that upgrading from standard to enterprise deleted the master key in the secure store. It was a good thing that I had recorded it when I first set it up a few days before. After providing the original master key all of credentials that I had set up reappeared.
|
-
Background and Problem
Recently I had to change the content type of many files within a SharePoint web site as part of a larger SharePoint 2007 to 2010 migration. SharePoint does not provide a simple way to do this for many files.
The Solution
I wrote a simple solution to do the switch.
function Reset-SPFileContentType ($WebUrl, $ListName, $OldCTName, $NewCTName)
{
#Get web, list and content type objects
$web = Get-SPWeb $WebUrl
$list = $web.Lists[$ListName]
$oldCT = $list.ContentTypes[$OldCTName]
$newCT = $list.ContentTypes[$NewCTName]
$newCTID = $newCT.ID
#Check if the values specified for the content types actually exist on the list
if (($oldCT -ne $null) -and ($newCT -ne $null))
{
#Go through each item in the list
$list.Items | ForEach-Object {
#Check if the item content type currently equals the old content type specified
if ($_.ContentType.Name -eq $oldCT.Name)
{
#Check the check out status of the file
if ($_.File.CheckOutType -eq "None")
{
$_.File.UndoCheckOut()
}
#Change the content type association for the item
$_.File.CheckOut()
write-host "Resetting content type for file" $_.Name "from" $oldCT.Name "to" $newCT.Name
$_["ContentTypeId"] = $newCTID
$_["Date of Document"] = $_["Date of the Document"]
$_.SystemUpdate()
$_.File.CheckIn("System update: Content type changed to " + $newCT.Name, 1)
}
else
{
write-host "File" $_.Name "is associated with the content type" $_.ContentType.Name "and will not be modified"
}
}
}
else
{
write-host "One of the content types specified has not been attached to the list"$list.Title
}
$web.Dispose()
}
To use this function, you can call the function within the same script file as follows:
Reset-SPFileContentType("http://dev.example.org/sites/MyNewSite ", "Sales Reports", "Document", "Sales Report Document")
This will open the site mentioned and will change all files in the Sales Reports document library from the content type of “Document” to “Sales Report Document”.
|
-
The Problem
I came across a situation where I needed to remove certain ribbon buttons for certain document libraries. I could have gone the route of adding code to the master page either on the server side or javascript that would run on the client side. Since the document libraries would be numerous and since that sort of one-off page modification would not work well when a list view web part was dropped on a random page anywhere on the site.
The Solution
I was able to solve my problem by creating a SharePoint solution (WSP) with a custom list template that would specify a customize the ribbon for any document library of that type. I would then only have to create my document libraries from that type and I would get my customized ribbon regardless of whether I was on a list view page or any other page with a list view web part. Feature.xml
<?xml version="1.0" encoding="utf-8" ?>
<Feature Id="f891e3ae-2983-4e7e-961f-c18e4f915da5"
Title="Hide Ribbon Buttons"
Description="Hides ribbon buttons for the custom list type"
Version="1.0.0.0"
Scope="Web"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="Manifest.xml" />
</ElementManifests>
</Feature>
Manifest.xml
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<CustomAction
Id="HideRibbonButtons"
Location="CommandUI.Ribbon"
RegistrationType="List"
RegistrationId="0x0101000BB3F82E2A624B77833C28045F765D7B">
<CommandUIExtension>
<CommandUIDefinitions>
<CommandUIDefinition Location="Ribbon.Library.Actions.OpenWithExplorer" />
<CommandUIDefinition Location="Ribbon.Documents.New.AddDocument.Upload.Upload" />
<CommandUIDefinition Location="Ribbon.Documents.New.AddDocument.Upload.UploadMultiple" />
<CommandUIDefinition Location="Ribbon.Documents.New.NewDocument" />
<CommandUIDefinition Location="Ribbon.Documents.New.AddDocument" />
<CommandUIDefinition Location="Ribbon.Documents.New.UploadDocument" />
</CommandUIDefinitions>
</CommandUIExtension>
</CustomAction>
</Elements>
Additional Notes This same technique can be used to modify the URLs, the text or the icon for them. That still leaves the issue of the "+ Add Document" link that the list view web part produces. Unfortunately, for that it seems that we have to resort to client-side script. SharePoint does not provide a way to globally modify the output of the list view web part in this respect for a particular document library, or type of document library. Furthermore, the link to upload.aspx appears to be hard coded in the web part.
|
-
Background
Recently I wrote a solution for a customized file upload page for SharePoint, replacing upload.aspx for only certain libraries. There was a requirement that the maximum file upload size for these libraries had to be 200 MB.
The Problem
The general upload size limits can be set in SharePoint at the web application level. The default for SharePoint is 50 MB, and in our case if there is a need to change it for the web application, we will probably want to set it to a number independent of the customized libraries. Therefore the SharePoint setting wasn't going to be fine-grained enough for us.
When we created the solution for the customized file upload page, we added that aspx file to the 14 hive. One of the reasons for placing it there is that in the 14 hive we can use a SharePoint solution (WSP) deploy our own folders and web.config files. We may have otherwise preferred to add it somewhere in the content database, but then we would not have the flexibility of web.config files and its settings.
The Solution
One of the settings in the web.config is maxRequestLength.
<configuration>
<system.web>
<httpRuntime
useFullyQualifiedRedirectUrl="true|false"
maxRequestLength="size in kbytes"
executionTimeout="seconds"
minFreeThreads="number of threads"
minFreeLocalRequestFreeThreads="number of threads"
appRequestQueueLimit="number of requests"
versionHeader="version string">
</httpRuntime></system.web></configuration>
This value and any of the others shown above can be set at the folder-level by placing the web.config in the folder with the ASPX files that it will apply to or you can limit the application of the setting to a particular ASPX page using the following hierarchy:
<configuration>
<location path="upload_foobar.aspx">
<system.web>
<httpRuntime
useFullyQualifiedRedirectUrl="true|false"
maxRequestLength="size in kbytes"
executionTimeout="seconds"
minFreeThreads="number of threads"
minFreeLocalRequestFreeThreads="number of threads"
appRequestQueueLimit="number of requests"
versionHeader="version string">
</httpRuntime></system.web></configuration>
The maxRequestLengh is a number in kilobytes that specifies the largest file size that IIS will accept. That file data will be passed into the customized ASPX page that will use the SharePoint object model to add the file to the folder. Internally the SharePoint object model will not limit the file size (except to the absolute maximum of 2 GB).
|
-
Background
SharePoint 2010 allows for extensive modification of the application. One of the areas where there seems to be a lack of customizability is the file upload page _layouts/upload.aspx. Sometimes it is necessary to provide a customized upload page for a certain web application, site, list or even folder.
The Problem
At a client where we have created a SharePoint-based document management and collaboration portal, we needed to restrict the way in which users could upload files. The goal was to ensure that any file uploaded would be placed in the correct folder and that metadata would be set by the user and also according to certain rules. We needed to ensure that only bottom-level folders (those that do not contain sub-folders) contain files. Out of the box, SharePoint will not restrict file upload to certain folders but not others. Permissions would be an acceptable solution to this, except that with many libraries and many SharePoint groups, keeping these permissions up to date would have needed a programmatic solution anyway. We also needed to ensure that the user could be restricted to using only certain metadata values depending on the folder. In other words the metadata for all files in a folder had to be consistent with that folder. Another requirement was that no file could be uploaded without metadata. Out of the box, SharePoint allows uploading. You can restrict checking in so that all metadata has to be entered. The issue is that many SharePoint users are confused by this process and will simply upload, press Cancel on the Properties dialog box and think that they are done. Lastly, there was a requirement to use rule-based managed metadata tagging so that the files could also be automatically tagged based on where they were placed.
The Solution The best solution to this issue seemed to be a customized file upload page. SharePoint only supports two ways to customize this page. The first is to modify the upload.aspx page in the 14 hive. While changing any out of the box pages is never recommended and may be overwritten in an upgrade or patch, it simply would not meet our needs because we only wanted this functionality on certain libraries within one particular web application. The other method is adding a new custom aspx page in the 14 hive. We decided to do this with a SharePoint WSP solution. The next hurdle was to modify the URL in the New Document button, Upload Document button, and Add a document link. The only one that SharePoint allows us to customize is the New Document button by simply setting the URL to the "template". The other URLs appear to be hard-coded or may be buried somewhere in site definition. Even if they are in the site definition, creating a new one and updating it there is more trouble than it is worth. Particularly because it would apply to all document libraries. We instead decided to create a custom IIS Rewrite Provider. This provider is simply a class that handles potential URL rewrite requests. We configure IIS on each WFE so that it uses this provider whenever a request for upload.aspx comes in for the correct web application. Our class simply takes the entire request URL and uses the embedded list ID and the path to determine the SharePoint site, find the list and make sure that it has a particular custom content type attached to it. If it does, then it will redirect from upload.aspx to upload_foobar.aspx. This way we can enable this feature on the library by simply adding our custom content type and no system pages or master pages need to be customized. The end user simply clicks on the upload.aspx links and the URL Rewrite Provider class redirects only for the appropriate libraries.
|
-
Background
Sometimes SharePoint administrators would like to update content in their site in certain ways, but don't want everything to show up as being last modified by them by name. Instead, they would prefer that the record simply show that it was last updated by System Account. On the other hand, sometimes administrators are not sure why SharePoint shows them as being logged in as System Account and why everything they edit or upload is tagged as such.
The Problem
It is not immediately clear in SharePoint when the user will be shown as System Account. The options to control this are also not immediately obvious. At times the problem is even rooted in what account is being used to run the application pool for a web application.
If the identity of the application pool is that of a particular administrator or if the administrator logs in as the farm account or the application pool account, then SharePoint will show that person as System Account. If none of these three is the case then SharePoint checks the web application's User Policies. The User Policies can specify certain access permissions for all content within web application and specifies whether users are shown as System Account. These settings are configurable by zone and can apply to AD Users and AD Groups. If the logged in user and the groups he or she is part of are specified in the User Policies as System Account, then SharePoint will display the person's name and update records with their username instead of System Account.
How to change it
If your issue stems from the User Policies then you have to make the change in Central Administration. Navigate to the Manage web applications page, which can be found at that this URL:
http://CentralAdminHostName:PortNumber/_admin/WebApplicationList.aspx
Select the web application that you would like to modify and click on the User Policy button on the ribbon. That will show the Policy for Web Application dialog box where you can add an AD group or user and set the proper options for them. The option labeled as Account operates as System, when checked, will show the user as System Account.
If you issue stems from the fact that your web application is running as your personal user account, then you may have not set up SharePoint as recommended. You can use the following stsadm command (for which there is no PowerShell cmdlet).
stsadm -o updatefarmcredentials ... Described here: http://technet.microsoft.com/en-us/library/cc262150(office.12).aspx
Followed by:
iisreset
If you found this post helpful, please rate it using the stars at the top. Thanks.
|
|
|
|