SharePoint Fanatic

SharePoint insights, real world experience

Category Archives: Content Types

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:\PowerShell\CORE\reporting\LibSettings.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 "Site\sAssets|Photo|Image|Customized\sReports|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

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

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

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

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

Introduction

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

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

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:\PowerShell\Logging\MetadataDefaults$($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|Site\sAssets|Customized\sReports|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

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.

Eliminating metadata from the Folder Content Type

Once again I was compelled to swoop in to fix a managled set of Metadata and Content Types for a key document library.  The simplest of the challenges was removing metadata from Folders, or so I thought.  In this case, somehow, all the fields in the Document Library were associated with the Folder Content Type, so adding a new folder presented the user with roughly ten fields to complete; which were not needed.

First attempt was to hide the fields, which didn’t succeed:

$Fields=$tContentType.Fields;
$Fields[$Field].set_showinaddform($false);
$Fields[$Field].set_showineditform($false);

I then tried the old standby of  trying to remove the field, where $i is the index of the field within the Content Type:

$Fields=$tContentType.Fields;
$Fields.item($i).remove()

I then tried my usual approach to redo the FieldLinks that is the list of fields to present for forms, by removing the selected fields, to no avail:

$fieldLinks = $tContentType.FieldLinks; #simple shortcut reference $fieldList = New-Object 'System.Collections.Generic.List[System.String]' #new ordered list of fields 
# add everything to the list EXCEPT the fields to be removed
for ($i = 1; $i -le $FieldLinks.Count; $i++) {
  $FieldName=$FieldLinks[$i - 1].Name;
 If ($FieldsToEliminate -notcontains $FieldName)  #if this is a field that belongs     
   {$fieldList.Add($FieldName);
}
$FieldLinks.Reorder($fieldList.ToArray())  #convert fields to array as we trigger Reorder method on fields  
$tContentType.Update()

By this time, I was getting pretty hot under the collar, spending hours wrestling with removing a few fields from a Folder Content Type.  This simple script did the trick.  Here it is as a function.  Note how I defined the parameter types, and also take care to clear and restore the ReadOnly flag, for any Content Type that started out Read-Only:

Function Eliminate_Visible_Fields_ContentType( [Microsoft.SharePoint.SPContentType]$tContentType
                                        , [System.string]$tFields
                                        ) {
 $originallyReadonly = $tContentType.readonly;
 if ($originallyReadonly) {$tContentType.set_readonly($false)};

 $FieldsToEliminate = $tFields.Split(“,”) #convert set of fields into array
 $Fields=$tContentType.Fields;
 foreach($field in $FieldsToEliminate)     {
   $tContentType.FieldLinks.delete($field); }
 Write-Output([System.String] "Content Types stripped!")

 $tContentType.Update()
 if ($originallyReadonly) {$tContentType.set_readonly($true)};
}

A few points to note:

  • The input parameter tFields is a comma separated string of field names
  • The field name expected is the “Internal” field name.  That means the ugly name with the internal hex characters.  So my field “Project Num” can be deleted only by referencing “Project_x0020_Num”.
  • SharePoint Manager can be used to derive the intername field names
  • Referencing the RootWeb Field list can get you the internal field names programmatically
  • Trying to delete the public and internal names is safe.  Best is to add them both to the list to be deleted; the correct one will be deleted.
Follow

Get every new post delivered to your Inbox.

Join 64 other followers