Creating Fields and Content Types cleanly in SharePoint

when creating Content Types in SharePoint, it’s nice to script the process, for planning and repeatability. Here’s a nifty routine that allows a single line creation of Site Columns and Content Types.

First, here’s the routine to create Site Columns. The here-string XML is replaced with placeholders filled by parameters. Note the convenient parameters:

function tryAdd-TextField ([Microsoft.SharePoint.SPWeb] $web, [string] $DisplayName, [string] $InternalName, [string] $Group, [Boolean] $Hidden, 
[Boolean] $Required, [Boolean] $ShowInDisplayForm, [Boolean] $ShowInEditForm, [Boolean] $ShowInNewForm )
{
$fields = $web.fields;
try
{
$q=$fields.getFieldByInternalName($InternalName); 
} catch
{
$q=$null;
}
if ($q -ne $null)
{
Write-Host "$($InternalName) already exists!, no action taken to create this site column"
}
else
{
$fieldXMLString = '<Field Type="Text"
Name="@InternalName" 
DisplayName="@DisplayName"
StaticName="@InternalName"
Group="@Group"
Hidden="@Hidden"
Required="@Required"
Sealed="FALSE"
ShowInDisplayForm="@ShowInDisplayForm"
ShowInEditForm="@ShowInEditForm"
ShowInListSettings="TRUE"
ShowInNewForm="@ShowInNewForm">
</Field>' 
$FieldXMLString = $FieldXMLString.Replace("@InternalName",$InternalName.tostring())
$FieldXMLString = $FieldXMLString.Replace("@DisplayName",$DisplayName.tostring())
$FieldXMLString = $FieldXMLString.Replace("@Group",$Group.tostring())
$FieldXMLString = $FieldXMLString.Replace("@Hidden",$Hidden.tostring())
$FieldXMLString = $FieldXMLString.Replace("@Required",$Required.tostring())
$FieldXMLString = $FieldXMLString.Replace("@ShowInDisplayForm",$ShowInDisplayForm.tostring())
$FieldXMLString = $FieldXMLString.Replace("@ShowInEditForm",$ShowInEditForm.tostring())
$FieldXMLString = $FieldXMLString.Replace("@ShowInNewForm",$ShowInNewForm.tostring())
$web.Fields.AddFieldAsXml($fieldXMLString)
}
}
function tryAdd-CT ([Microsoft.SharePoint.SPWeb] $web, [string] $Field, [string] $Group, [string] $Parent, [string] $Name, [string] $Description )
{
try
{
$ctypeParent = $web.availablecontenttypes[$Parent]
}
catch
{
$ctypeParent = $null;
}
if ($ctypeParent -eq $null)
{
write-host "Content Type $($Name) not created because there was a problem finding the Parent Content Type $($Parent)"
}
else
{
$ctype = new-object Microsoft.SharePoint.SPContentType($ctypeParent, $web.contenttypes, $Name)
$ctype.Description = $Description
$ctype.group = $Group
if (![string]::IsNullOrEmpty($field))
{
foreach ($fld in $field.split("|"))
{
$f=$web.fields.getFieldByInternalName($fld)  
$link = new-object Microsoft.SharePoint.SPFieldLink $f
$ctype.FieldLinks.Add($link)
}
}
try
{
$ctype = $web.contenttypes.add($ctype)
}
catch
{
write-host "Content Type $($Name) already exists"
}
}
}

Let’s now create some site columns:

tryAdd-TextField -web $Web -DisplayName "Year"    -InternalName "YearACT"    -Group "Actuarial" -Hidden $False -Required $False -ShowInDisplayForm $True -ShowInEditForm $False -ShowInNewForm $False 
tryAdd-TextField -web $Web -DisplayName "Quarter" -InternalName "QuarterACT" -Group "Actuarial" -Hidden $False -Required $False -ShowInDisplayForm $True -ShowInEditForm $False -ShowInNewForm $False 
tryAdd-TextField -web $Web -DisplayName "Month"   -InternalName "MonthACT"   -Group "Actuarial" -Hidden $False -Required $False -ShowInDisplayForm $True -ShowInEditForm $False -ShowInNewForm $False 
tryAdd-TextField -web $Web -DisplayName "LOB"     -InternalName "LOBACT"     -Group "Actuarial" -Hidden $False -Required $False -ShowInDisplayForm $True -ShowInEditForm $False -ShowInNewForm $False 

Here’s how to create a Content Type with one field:

tryAdd-CT -web $Web -Field $null -Group "Actuarial"  -Parent (select-parent)  -Name "My Core ACT" -Description "Core Actuarial Document"

Note in the comment below, the fields can be passed in pipe-delimited

tryAdd-CT -web $Web -Field "YearACT|QuarterACT|MonthACT|LOBACT" -Group "Actuarial"  -Parent "My Core ACT" -Name "General (ACT)" -Description "General Actuarial Document"

Report on every library in a SharePoint Farm

Report on every library in a SharePoint Farm

It is very useful to be able to examine attributes from every library in a SharePoint farm. Here’s how to generate a report of useful library summary information, such as what level of versioning is enabled, which libraries are configured for QuickLinks and which have Content Types enabled, and how many documents are within each library. I use pipe-separated fields, in case commas are encountered in library fields, such as titles.

Add-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue
$mylogfile="L:PowerShellCOREreportingLibSettings.csv"
$envrun="Prod"			# selects environment to run in
if ($envrun -eq "Dev")
{
$siteUrl = "http ://SharePointDev/"
}
elseif ($envrun -eq "Prod")
{
$siteUrl = "http ://sharepoint/"
}
else
{
Write-Host "ENVIRONMENT SETTING NOT VALID: script terminating..."
$siteUrl =  $null;
return;
}
Write-Host "script starting" 
$myheader = "STARTING: $(get-date)"
$Sep="|"
add-content $mylogfile "Site$($sep)Web$($sep)Library$($sep)FileCount$($sep)CTEnabled$($sep)VerMajor$($sep)VerMinor$($sep)QuickLaunch$($sep)MajVerLimit$($sep)MinorVerLimit$($sep)UniquePerms"
$WA=Get-spWebApplication $siteUrl;
foreach ($site in $wa.sites)
{
write-host $site.Url
foreach ($web in $site.allwebs)
{
if ($true) #useful placeholder for filtering. 
{
Write-Host -foregroundcolor darkgreen "$($site.id) - $($site.Url) - $($site.contentdatabase.id) - $($site.contentdatabase.name)"   
for ($i=0;$i -lt $web.Lists.Count;$i++)
{ 
$JPLib=$web.Lists[$i];
$A_Lib_Count++;
$SkipLib=$true; #true
if ( ($JPlib.BaseType -ne "DocumentLibrary") -or ($JPlib.hidden) )
{
# forget the rest and return to top
Write-Host -foregroundcolor green "fast test skipping Library: $($JPlib)";   
}
elseif ($JPLib.Title -Match "SitesAssets|Photo|Image|CustomizedsReports|Templates|Pages|Picture|cache|style|Slide")
{
# forget the rest and return to top
Write-Host -foregroundcolor red "fast test skipping Library because it mentions $Matches: $($JPlib)";   
}
elseif ($JPLib.BaseTemplate -ne "DocumentLibrary")   #alternatively, only skip if -eq XMLForm
{
# forget the rest and return to top
Write-Host -foregroundcolor red "fast skipping Library because it is not of base DocumentLibrary, it is BaseType:$($JPlib.basetemplate): $($JPlib.title)";   
}
elseif (($JPLib.ThumbnailsEnabled) -or ($JPLib.DefaultView -eq "AllSlides"))
{
# forget any library with thumbnails, these are not normal doclibs, and return to top
Write-Host -foregroundcolor red "fast test skipping Library because it has Thumbnails/Slides $($JPlib)";   
}
else
{  $SkipLib=$false;	}
if (!$SkipLib)
{
#write-Host -foregroundcolor green "Processing Library: $($JPlib)";   
$LineOut = "$($site.url)$($sep)$($web.title)$($sep)$($JPLib.title)$($sep)$($JPLib.items.count)$($sep)$($JPLib.ContentTypesEnabled)$($sep)$($JPLib.get_EnableVersioning())$($sep)$($JPlib.get_EnableMinorVersions())$($sep)$($JPLib.OnQuickLaunch)$($sep)$($JPLib.MajorVersionLimit)$($sep)$($JPLib.MajorWithMinorVersionsLimit)$($sep)$($JPLib.HasUniqueRoleAssignments)"
Write-Host $lineOut
add-content $mylogfile $LineOut
}
}
} #foreach site
} #if $true/Siteurl is not null, if environment setup is valid
} #foreach letter
###########################

Programatically reassigning Content Types in a library

Reassigning Content Types in a library using PowerShell

It is useful to be able to change the Content Type for each document in a library to a new Content Type. Perhaps you are consolidating, or just moving to a new Content Type hierarchy. There are a few tricks to changing a Content Type for a document. First, you’ll need to force a check-in if a document is checked out. Next, you’ll want to reference the Content Type by ID, specifically using this method: $list.Items.GetItemById($item.ID). Any other attempt to reassign a Content Type will fail. Lastly, either do a systemupdate() or clean up after yourself by restoring the timestamp, editor, and delete interim versions. Note there needs to be sufficient metadata, or the change will result in an error and/or the document being left checked out. This function traps and reports this condition. Here’s the function we will use:

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)
{
$ForcedCheckin=$false;
if ($_.File.CheckOutType -ne "None")
{
try { 
$_.File.CheckIn(“Checked In By Administrator”);
write-host -foregroup Yellow "Forced checkin for $($_.File.Title)"
}
catch { 
write-host -foregroup Red "FAILED to Force checkin for $($_.File.Title)"
$ForcedCheckin=$false;
}
}
#Check the check out status of the file
if ($_.File.CheckOutType -eq "None")
{
$item=$_
[System.DateTime]$date = $item["Modified"]
$user = New-Object microsoft.SharePoint.SPFieldUserValue($web, $item["Editor"])
try { #untested try, failure could be due to inadequate required metadata
#Change the content type association for the item
[Microsoft.SharePoint.SPSecurity]::RunWithElevatedPrivileges(
{
$item2 = $list.Items.GetItemById($item.ID);
#CHECKOUT NOT REALLY NEEDED, IF MUSTCHECKOUT IS DISABLED EARLIER
#        $item2.File.CheckOut()
#write-host $item2['Name']
Write-Host "." -NoNewline
$item2["ContentTypeId"] = $newCTID
$item2.Update()
$item2["Modified"] = $date;
$item2["Editor"] = $user;
$item2.Update()
#get rid of the last two versions now, trapping any errors 
try { $item2.Versions[1].delete() } catch {write-host -foregroundcolor red "Error (1) could not delete old version of $($item2['Name'])"}
try { $item2.Versions[1].delete() } catch {write-host -foregroundcolor red "Error (2) could not delete old version of $($item2['Name'])"}
if ($ForcedCheckin)
{try { $item2.Versions[1].delete() } catch {write-host -foregroundcolor red "Error (3) could not delete ForcedCheckin version of $($item2['Name'])"}}
#get rid of the last version now
#        $item2.File.CheckIn("Content type changed to " + $newCT.Name, 1)
} )
} catch { write-host -foregroundcolor red "Error (possibly inadequate metadata) updating file: $($_.Name) from $oldCT.Name to $($newCT.Name)"
}
}
else
{
write-host -foregroundcolor red "File $($_.Name) is checked out to $($_.File.CheckedOutByUser.ToString()) and cannot be modified";
}
}
else
{
#Write-Host -ForegroundColor DarkRed "File $($_.Name) is associated with the content type $($_.ContentType.Name) and shall not be modified `n";
}
}
}
else
{
write-host -foregroundcolor red "One of the content types specified has not been attached to the list $($list.Title)"
}
$web.Dispose()
}

Next, let’s just call the function passing in the old name and the new name:

reset-spfilecontenttype -weburl $weburl -listname $JPlib  -oldctname "Documents" -newctname "DOCS"

Removing a stubborn Content Type from a SharePoint Library

Removing a Content Type from a SharePoint Library

Having a single content type in a library can make a library sing for end-users, by avoiding choices and prompts. However removing an existing Content Type can be a problem. Primarily, if it is still in use, SharePoint will not allow its removal with “Content Type is Still in Use”. The first thing to do is to clear the recycle bin, if feasible. Microsoft reports this isn’t necessary, but when the Content Type hits the fan, we have to try a few things.

I recently found a case where the following PowerShell still failed, when trying to delete two ways, even with enabling unsafe updates:

$web = Get-SPWeb $WebUrl
$web.set_AllowUnsafeUpdates($true)
$list = $web.Lists[$ListName]
$oldCT = $list.ContentTypes[$OldCTName]
$oldCTID = $oldCT.ID   #fails, still in use
$list.ContentTypes.Delete($oldCTID) #fails, still in use
$oldCT.delete()
$web.set_AllowUnsafeUpdates($false)
$web.Dispose()

I finally tracked it down to documents that were checked out to a user, and had never before been checked in. Without a checked in version, they weren’t visible. The trick is to take ownership of these, then change their content types. Only then can the unused Content Type be deleted.

To automate the reassignment of a Content Type, see my next blog article: Reassigning Content Types programmatically.

Content Type Syndication tips from the Masters

Content Type Syndication must-know tips

Content Type Syndication is a must for enterprise-class structured SharePoint document management systems.

There are some limitations to be aware of.   A site column has an internal name and an external name. The internal name is set (and never changes) after the Site Column is created. So if you create a site column called “my data” it might appear to have a site column name of “my%20data” with %20 being the hex 20 equating to 32 for ASCII space. Now if you have 100 site collections, and one of them already has a field called “my data”, perhaps it is of a different type (number vs text, for example) then the Content Syndication Hub will not be able to publish this one site column, and will show it in a report available online. Within your syndication hub, under Site Collection Administration, is an entry you can click called “Content type service application error log”. This is a list of errors where such site columns could not be published. During the publishing of Content Types, there’s actually a “pre-import check” that is done. The frustrating part, is when you create a site column, you don’t always know whether that name is in use somewhere. This is done for both the external visible name and the cryptic internal site column name.

When you publish a Content Type, it doesn’t get deployed right away. There’s an hourly batch job for each Web Application. You can go into Central Admin, Monitoring, Timer Jobs, and force the job to run for your web application. It’s called “Content Type Subscriber”. This picks up any recently published Content Types, and “pushes” them down throughout the Site Collection, starting with replicating the Site Columns, Content Types, Information Policies, then pushing them into sites and libraries, if the published Content Type is set to propagate down to lists (that’s a check box in the Content Type).

Similarly, within the subscribing Site Collection, in the Site Collection Administration, there’s a “Content type publishing error log” that summarizes issues with propagating Content Types and Site Columns.

For both Content Type Publishing, of many Content Types, I usually script the publishing, as well as running the subscribing jobs. I find that works extremely well, and avoids human errors.

On the  home page for the Content Type Syndication Hub I always put a link to:

1. Site Columns

2. Content Types

3. The Central Admin Timer Jobs, or a link directly to the main Web Application’s “Content Type Subscriber” Timer Job.

There’s a lot more, but let me know if there are any aspects I can clarify further.

How to delete a hidden library field in SharePoint

Deleting a hidden library field in SharePoint

Hidden fields do not appear in the user interface when viewing Library settings. One reason to delete a hidden field is after a field is removed from a Content Type in a syndication hub, the field remains in the libraries associated with the original Content Type, although it is now “orphaned” and unrelated to the Content Type. The solution is to remove the field itself from each and every library. The script commands below can easily action all libraries based on an iteration through all Web Apps, Sites, Webs and Libs.

A hidden field can be deleted via PowerShell, but one should note a few obstacles to work around. First, the field needs to be unhidden, albeit temporarily. Then the field object needs to be updated. Only then can the field be deleted. Here’s how:

First, let’s get the Web, the list within it, and the target field:

$JPWeb = Get-SPWeb "http ://sharepoint/div/Path/Site"
$lists = $JPWeb.lists
$list = $lists["MyList"]
$fields=$list.fields

Let’s have a peek at the friendly name and internal name of each field in the list, by piping to a select:

$fields | select title, staticname

Let’s grab the target field, and start by unhiding it, updating the field, then deleting it:

$field = $fields["FieldToDelete"]
#$field.delete() #can't delete a hidden column
$field.set_Hidden($false)
$field.Update()
$field.delete() 

The above code has a bit of a risk. External field names can change. Oddly a number of internal field names have the same external name. These are known as “Title” values in the object model. The best approach when deleting a field is to use the Static name. Here’s how:

$field = $list.Fields.GetFieldByInternalName("InternalFieldName")

Best practice is to use a try/catch, as the above function will throw an error if the field is not found.

That’s it. No further updates are necessary to the List or Web objects. If you have content types referencing this field, the local copy of the content type within the List is customized via the removal of this field.

Add a Taxonomy Field into Every Site Collection and to a Content Type

Add a Taxonomy Field into Every Site Collection

I recently had to add a Taxonomy Site Column into a range of Site Collections, and then add the new Site Column into a Content Type.  Lastly, I needed to update all libraries in the site collection to reflect the updated Content Type.  How might we do this?

Step by step

Let’s get a web application, and its sites.  Note that this is not the most efficient approach, as a collection of SPSites can be large.  A more efficient way could be to pass the Sites through a pipeline.

$webApp=Get-Spwebapplication "http ://SharePoint" #your web app url
$sites=$webApp.sites;

Let’s loop through the SPSites, and get the SPWeb to work with.  The SPSite actually has no content, nor does it have Site Columns or Content Types.  The root web is where Site Columns and Content Types are managed.

for ($i=0; $i -lt $sites.count; $i++)
{
$site= $sites[$i];
$JPweb=$site.rootweb;
}

To do anything with Managed Metadata requires opening a Taxonomy session.   This can be done directly through the Service Application.  For simplicity, I open the session at the Site level.  This code assumes only one Managed Metadata Service association (index offset of zero).  If you have more than one MMS service application, query the collection of termstores to ensure you are grabbing the correct one.  Note below there is a “group name” and “Term Set Name” required.  Adjust these to match your environment.  Lastly there’s a check below to ensure you grabbed a termset, and aren’t holding a null reference:

$taxSession = new-object Microsoft.SharePoint.Taxonomy.TaxonomySession($site, $true);
$termStore = $taxSession.TermStores[0];
$TermSet = $termStore.Groups["Your Term Group"].TermSets["TermSetName"]
if ($TermSet -eq $null)
{
Write-Host -ForegroundColor DarkRed "Termset does not exist"
continue;
}

Now let’s create a new field. Note we’ll need to know the display name for the field, as well as define a static (internal) name.  As with all Site Columns, I highly recommend using a static name that avoids spaces and other characters that lead to control escape sequences in the code (%20 for space, etc).  Note the termset that is “latched” to this field refers to the specific term store (it could point to any of your Managed Metadata Service Applications) and to the specific termset.  These are GUIDs.  Note that each Managed Metadata Service Application is associated with a dedicated database.  That’s where your termsets are stored.  You’ll want to select a “group” for where the site column will be displayed.  We’ll add the field, and update the SPWeb object.

$taxonomyField = $JPweb.Fields.CreateNewField("TaxonomyFieldType", $FieldToAddDisplayName)
$taxonomyField.SspId = $termSet.TermStore.Id
$taxonomyField.TermSetId = $termSet.Id
$taxonomyField.AllowMultipleValues = $false
$taxonomyField.Group = "Site Column Group"
$taxonomyField.StaticName = $FieldToAddStaticName
$taxonomyField.ShowInEditForm = $true
$taxonomyField.ShowInNewForm = $true
$taxonomyField.Hidden = $false
$taxonomyField.Required = $false
$JPweb.Fields.Add($taxonomyField);
$JPweb.Update();

Now let’s grab the set of Content Types, find our target Content Type and field, and update the Content Type.  We’ll turn off ReadOnly for the Content Type, and do the following special object update to force propagation of the Content Type into all the libraries in the site collection: $ct.UpdateIncludingSealedAndReadOnly()

$cts=$JPWeb.ContentTypes
$ct=$cts["Specific Content Type"] # replace with your content type
$ct.set_ReadOnly($false)
$fields=$ct.fields
$fs=$JPWeb.Fields
$favField=$fs.get_Item($FieldToAddDisplayName)
if ($favField -eq $null)
{
Write-Host -ForegroundColor DarkRed "Cannot find $($FieldToAdd) in web $($JPWeb.url)"
continue;
}
$link = new-object Microsoft.SharePoint.SPFieldLink $favField
$ct.FieldLinks.Add($link)
$ct.UpdateIncludingSealedAndReadOnly($true)
$ct.set_ReadOnly($true)

Let’s now put it all together into one neat script that includes some extra error handling:

$FieldToAddStaticName = "InternalFieldName"
$FieldToAddDisplayName = "Field Display Name"
$webApp=Get-Spwebapplication "http ://SharePoint" #your web app url
$sites=$webApp.sites;
for ($i=0; $i -lt $sites.count; $i++)
{
$site= $sites[$i];
$JPweb=$site.rootweb;
if ($site.url -notlike "http ://SharePoint/MyPreferredPath/*")
{
continue;
}
$taxSession = new-object Microsoft.SharePoint.Taxonomy.TaxonomySession($site, $true);
$termStore = $taxSession.TermStores[0];
$TermSet = $termStore.Groups["Your Term Group"].TermSets["TermSetName"]
if ($TermSet -eq $null)
{
Write-Host -ForegroundColor DarkRed "Termset does not exist"
continue;
}
$taxonomyField = $JPweb.Fields.CreateNewField("TaxonomyFieldType", $FieldToAddDisplayName)
$taxonomyField.SspId = $termSet.TermStore.Id
$taxonomyField.TermSetId = $termSet.Id
$taxonomyField.AllowMultipleValues = $false
$taxonomyField.Group = "Site Column Group"
$taxonomyField.StaticName = $FieldToAddStaticName
$taxonomyField.ShowInEditForm = $true
$taxonomyField.ShowInNewForm = $true
$taxonomyField.Hidden = $false
$taxonomyField.Required = $false
$JPweb.Fields.Add($taxonomyField);
$JPweb.Update();
$cts=$JPWeb.ContentTypes
$ct=$cts["Specific Content Type"] # replace with your content type
if ($ct -eq $null)
{
Write-Host -ForegroundColor DarkRed "Cannot add field to Content Type in web $($JPWeb.url)"
continue;
}
$ct.set_ReadOnly($false)
$fields=$ct.fields
$fs=$JPWeb.Fields
$favField=$fs.get_Item($FieldToAddDisplayName)
if ($favField -eq $null)
{
Write-Host -ForegroundColor DarkRed "Cannot find $($FieldToAdd) in web $($JPWeb.url)"
continue;
}
$link = new-object Microsoft.SharePoint.SPFieldLink $favField
$ct.FieldLinks.Add($link)
$ct.UpdateIncludingSealedAndReadOnly($true)
$ct.set_ReadOnly($true)
}

Folder Metadata Web Part

Creating a Folder Metadata Web Part

An interesting challenge I faced was programmatically exposing custom folder metadata within a specific Document Library View, for specific Folder Content Types. As background, I already created Folder Content Types, deriving from the SharePoint Folder Content Type, to which I added appropriate metadata. These new Content Types were syndicated from a Content Type Hub, and propagated across the enterprise.

I extended the ASP template to include a table that I will build dynamically, and a button that I optionally hide:

<asp:Table
ID="Table1" runat="server">
</asp:Table<>asp:Button ID="PlaceholderButton" runat="server" Text="Create New Placeholder" onclick="PlaceholderButton_Click" />

I then add an action for the button:

protected void PlaceholderButton_Click(object sender, EventArgs e) 
{
Response.Redirect("http ://sharepoint/div/myDiv/Lists/MyListTasks/NewForm.aspx?RootFolder="); 
} 

This useful function adds a table row consisting pair of table cells with a property label (in bold) and the property value, but only if the property/value is found, on the current Document. Note Content Type is detected and handled separately:

void push_property(string myLabel, string propertyName)
{
try
{
string val = null;
if (propertyName == "ContentType")
val = folder.Item.ContentType.Name;
else
val = folder.Item.Properties[propertyName].ToString();
if (!String.IsNullOrEmpty(val))
{
TableRow rw = new TableRow();
TableCell cel = new TableCell();
cel.Text = string.Concat("<b>", myLabel, "</b>");
rw.Cells.Add(cel);
cel = new TableCell();
cel.Text = val;
rw.Cells.Add(cel);
Table1.Rows.Add(rw);
}
}
catch { }
}

This is the heart of this little web-part. I derive the actual folder from the user context, by extracting it from the browser URL string using Page.Request.QueryString[“RootFolder”]:

protected void Page_Load(object sender, EventArgs e)
{
bool ok = true;
string ctName = null;
SPWeb web = SPContext.Current.Web;
string rootFolder = Page.Request.QueryString["RootFolder"];
//Label2.Text = rootFolder;
if (String.IsNullOrEmpty(rootFolder))
ok = false;
if (ok)
{
folder = web.GetFolder(rootFolder);
if (!folder.Exists)
ok=false;
}
if (ok)
{
ctName = folder.Item.ContentType.Name;
if ((ctName != "Divison Acct Folder") && (ctName != "Divison Common Folder"))
ok=false;
}
PlaceholderButton.Visible = ok;  //reacts dynamically, needs setting in both directions, as it maintains state
if (ok)
{
//push_property("Folder Type", "ContentType");  //Handled in special fashion internal to function
push_property("Claimant", "Claimant");      
push_property("Account Number", "AccountNumber");
push_property("Issue Description", "IssueDescription");
/*Only apply this border logic if you want a griddy view
if (Table1.Rows.Count >  0)
{
Table1.GridLines = (GridLines) 3;
}
*/
}
}

Metadata Defaults for Folders

Setting Metadata Defaults for Folders

SharePoint Libraries can be configured with metadata defaults for documents added to specific folders.  Column Defaults are manually configurable in Library Settings.  For each folder that has metadata defaults, the folder is marked with a green stamp.  Any document added to the folder gets the default metadata by default.

Metadata Defaults configuration in Library Settings

This configuration can be done using PowerShell.  In this example, I have created Folder Content Types, by defining them as inheriting from the Folder Content Type, then adding metadata to the folder Content Types.  This makes it easy to then assign the metadata defaults.

 We can clear away any existing metadata defaults on a folder using these commands to grab a metadata defaults object, then call the RemoveAllFieldDefaults() method:

[Microsoft.Office.DocumentManagement.MetadataDefaults] $columnDefaults = new-object Microsoft.Office.DocumentManagement.MetadataDefaults($JPLib);
$nuthin=$columnDefaults.RemoveAllFieldDefaults($JPFolder.Folder); 
$columnDefaults.update()

We can assign a metadata default for one field for one folder using these commands:

[Microsoft.Office.DocumentManagement.MetadataDefaults] $columnDefaults = new-object Microsoft.Office.DocumentManagement.MetadataDefaults($JPLib);
$nuthin=$columnDefaults.SetFieldDefault($JPFolder.Folder, $InternalName, $AssignmentValue);
$columnDefaults.update()

Before we get to the script, it is very important to note that get SPSite and SPWeb object must be acquired using the precise correct case. It seems the case-sensitive XML underlying these calls is affected by incorrect case; the folders will get the green stamp, but the metadata defaults won’t be applied unless the case is exactly correct. To get around this problem, you can grab all SPSites for a Web Application, and pipe them to get the SPWebs, to process. The only problem with that approach is the extreme memory inefficiency. More than that, until the objects are totally released, the changes accrue in the SQL Transaction Log until released, allowing .NET to commit them.

Also note the SPFolder object does not require an update(), as the metadata default methods handle the update.

Here’s the full script:

#already matched to the Claim Folder Fields on 9/4/12 to confirm all necessary fields are included. no changes actually made.
#Added CT filtering on these folder CTs: Claim Acct Folder|Claims Common Folder
[system.reflection.assembly]::LoadWithPartialName("Microsoft.Sharepoint") 
$Action="WipeAndApplyDefaults"
#$Action="ApplyDefaults"
#$Action="WipeDefaults"
$FolderFields="Field1,Field2,Field3"
$FolderFieldsArr=$FolderFields.split(",");
$baseURL="http ://sharepoint/ManagedPath/"
$SiteURL="http ://sharepoint/ManagedPath/a"
$OutputSuffix="A"
$mylogfile=$mylogfile="L:PowerShellLoggingMetadataDefaults$($OutputSuffix).csv"
#$WebAppHeading="http ://sharepoint"
Write-Host ("$(get-date) Running script assign metadata defaults to folders")
if ($siteurl)
{
write-host $site.Url
$WebScope=start-SPAssignment 
$TargetWeb=$WebScope | Get-SPWeb $siteURL;
$ListOfLists = @();
# Loop through all doc libs
$lists=$TargetWeb.lists;
$listsCount=$lists.count
for ($ii=0; $ii -lt $listsCount; $ii++)
{
$JPlib=$lists[$ii];
if ( ($JPlib.BaseType -ne "DocumentLibrary") -or ($JPlib.hidden) )
{
# forget the rest and return to top
Write-Host -foregroundcolor darkred "fast test skipping Library: $($JPlib)";   
}
elseif ($JPLib.Title -Match "Photo|Image|SitesAssets|CustomizedsReports|Templates|Pages|Picture|cache|style")
{
# forget the rest and return to top
Write-Host -foregroundcolor darkred "fast test skipping Library because it mentions $Matches: $($JPlib)";   
}
else
{
$ListOfLists += $JPlib.title;
}
}
$TargetWeb.dispose();
$JPlib=$null;
stop-SPAssignment $WebScope
foreach ($CurrentLib in $ListofLists)
{
$WebScope=start-SPAssignment 
$TargetWeb=$WebScope | Get-SPWeb $siteURL;
$JPlib=$TargetWeb.lists[$CurrentLib];
$JPlib.title
if ($JPlib -eq $null)
{
Write-Host "COULD NOT GET LIB $($CurrentLib)"
continue;
}
$JPFolders=$JPlib.Folders;
$JPCount=$JPFolders.get_count();
for ($i=0; $i -lt $JPCount; $i++)  #Do not use ItemCount, that one includes folders!
{	
$JPFolder=$JPFolders[$i];
$CT=$JPFolder.contenttype.name;
if ($CT -notmatch "Folder CT1|Folder CT2")
{
Write-Host "+" -NoNewline
if (($Action -eq "WipeDefaults") -or ($Action -eq "WipeAndApplyDefaults"))
{
[Microsoft.Office.DocumentManagement.MetadataDefaults] $columnDefaults = new-object Microsoft.Office.DocumentManagement.MetadataDefaults($JPLib);
$nuthin=$columnDefaults.RemoveAllFieldDefaults($JPFolder.Folder); 
$columnDefaults.update()
}
if (($Action -eq "ApplyDefaults") -or ($Action -eq "WipeAndApplyDefaults"))
{
foreach ($FF in $FolderFieldsArr)
{
try
{
if ($JPFolder[$FF] -ne $null)
{
$TargetField=$JPFolder.Fields[$FF];
$InternalName=$TargetField.InternalName; 
if (($TargetField.type -eq "DateTime") -or ($TargetField.type -eq "Date"))
{
$AssignmentValue=Get-Date $JPFolder[$FF] -Format u; #Z style universal format for assignment
}
else
{
$AssignmentValue=$JPFolder[$FF].tostring()
} #Leaves open all kinds of field type challenges, such as Managed Metadata
if ($AssignmentValue -ne $null)
{
[Microsoft.Office.DocumentManagement.MetadataDefaults] $columnDefaults = new-object Microsoft.Office.DocumentManagement.MetadataDefaults($JPLib);
$nuthin=$columnDefaults.SetFieldDefault($JPFolder.Folder, $InternalName, $AssignmentValue); 
$columnDefaults.update()
}
}
}
catch 
{
Write-Host "problem with field $($FF)"
} #must have been a field I shouldn't be poking, no biggie
}
} #Action=ApplyDefaults
#folder update is not required, columnDefault update propagates immediately
}
} # loop for items
$JPlib.Update();
$JPlib=$null;		
$Targetweb.update()
$TargetWeb.dispose();
Stop-SPAssignment $WebScope
} #Only process this lib
}# Lib loop
Write-Host "SCRIPT COMPLETE $(get-date)"

Content Type Summary Report

Content Type Summary Report

Sometimes I get challenged with questions as to which fields are used in which Content Types.  All too often I need to know quickly know the internal name of fields used in Content Types.  I wrote a script that generates a report that you can run to generate a CSV that can easily be Pivoted in Excel for answering such questions. I’m a huge fan of using a Content Type Syndication Hub. With all the Content Types in one location, this report becomes very useful.

$rootwebname="http ://SharePoint"
$rootweb=Get-SPWeb $rootwebname
$MyCTSummaryCSV="L:CTSummary.CSV"
Add-Content  $MyCTSummaryCSV "CT Name,CT Group,Parent CT, CT Read-Only,CT Hidden,Field Internal Name,Field Title,Field Type,ShowInDisplayForm,ShowInEditForm,ShowInNewForm"
$CTs=$rootweb.contenttypes
for ($i=0; $i -lt $CTs.count; $i++)
{
$CT=$CTs[$i];
$CTName=$CT.Name;
$Fields=$CT.Fields;
for ($j=0; $j -lt $Fields.count; $j++)
{
$Field=$Fields[$j];
$OutStr="$($CTName),$($CT.group),$($CT.Parent.Name),$($CT.ReadOnly),$($CT.Hidden),$($Field.staticname),$($Field.Title),$($Field.type),$($Field.ShowInDisplayForm),$($Field.ShowInEditForm),$($Field.ShowInNewForm)"
Write-Host "." -NoNewline
Add-Content  $MyCTSummaryCSV $OutStr
#write-host "$($outstr)"
}
}

It’s easy to then import this into an Excel file and Pivot away.

Let’s take it one step further and report on which Content Types and fields are in use within each Content Type enabled library in every web of a Site Collection:

$rootwebname="http ://SharePointdev/div/inv"
$rootweb=Get-SPWeb $rootwebname
$MyCTSummaryCSV="C:UsersplautjDocumentsPowerShellINVCTSummary.CSV"
Add-Content  $MyCTSummaryCSV "web,lib,CT Name,CT Group,Parent CT, CT Read-Only,CT Hidden,Field Internal Name,Field Title,Field Type,ShowInDisplayForm,ShowInEditForm,ShowInNewForm"
$CTs=$rootweb.contenttypes
$site = Get-SPSite $rootwebname
$webs = $site | Get-SPWeb -Limit all
foreach ($web in $webs)
{
$libs = $web.lists;
foreach ($lib in $libs)
{
if ($lib.contenttypesenabled)
{
$CTs = $lib.contenttypes;
for ($i=0; $i -lt $CTs.count; $i++)
{
$CT=$CTs[$i];
$CTName=$CT.Name;
$Fields=$CT.Fields;
for ($j=0; $j -lt $Fields.count; $j++)
{
$Field=$Fields[$j];
$OutStr="$($web.title),$($lib.title),$($CTName),$($CT.group),$($CT.Parent.Name),$($CT.ReadOnly),$($CT.Hidden),$($Field.staticname),$($Field.Title),$($Field.type),$($Field.ShowInDisplayForm),$($Field.ShowInEditForm),$($Field.ShowInNewForm)"
Write-Host "." -NoNewline
Add-Content  $MyCTSummaryCSV $OutStr
#write-host "$($outstr)"
}
}
}
}
}