Automating creation of Per-Location Views

Automating creation of Per-Location Views

SharePoint offers the capability of customizing the Views in Folders.  This can be done manually.  This article will show you how to automate the configuration of views for each folder in a document library.

Configuring Per-Location Views Manually

To configure Per-Location Views manually, one must have at least library designer privileges.  First go into Library Settings, then select Per-Location Views:

For any View configured for the location, it appears as a folder with a green mark, indicating a custom Per-Location view:

Green mark for each per-location view

How SharePoint stores Per-Location Views

SharePoint stores the per-location view information in XML within a specific property (“client_MOSS_MetadataNavigationSettings”) of the RootFolder object of the list.   Here’s how to extract this XML and save it to a file for examination:

$RF=$List.RootFolder
$RF.Properties["client_MOSS_MetadataNavigationSettings"] > "L:1 before.xml"

Let’s look into the XML and see how Views are stored in this special property.  First note below that Views are referenced by the View GUID, the relative View URL, as well as an Index.  Positive indexes are available Views.  Negative Indexes are hidden Views, and the 0 index is the default View:

XML of per-location View before it is updated

The approach

The basic approach is to grab the XML described above, clean it up from any previous customization, and add folder nodes; and for each folder, add Views.

Step-by-Step, programmatically adding a Per-Location View

First, let’s grab the List, and Folder objects:

$web=get-spweb http ://SharePoint/MyWeb $lists=$web.lists
$list=$lists["ListName"]
$Folders=$list.Folders
$ViewSet="All Documents,EMail,Claim View,LC View,UW View"
$ViewArr=$ViewSet.split(",")</pre>

We can always save the XML for reference:

$RF.Properties["client_MOSS_MetadataNavigationSettings"] > "L:1 before.xml"

Now, let’s grab the Views object, the Root Folder object, and the per-location Views cast as XML:

$Views=$list.Views
$RF=$List.RootFolder
$x=$RF.Properties["client_MOSS_MetadataNavigationSettings"]

 

Now let’s wipe out any previous per-location Views customizations.  We will simply remove all nodes in the Folder

$x.MetadataNavigationSettings.NavigationHierarchies.FolderHierarchy.removeall()

Anytime we want to examine the XML, we can output it to file, using the InnerXML:

$x.InnerXml > "L:1 after.xml"

Normally, we would grab the  XMLElement using $x.MetadataNavigationSettings.NavigationHierarchies.FolderHierarchy, but this returns a string for a null or initialized node. 
So instead, we grab the parent (NavigationHierarchies), then select the underlying single nodes:

$NavHierNode = $x.MetadataNavigationSettings.NavigationHierarchies

$ViewSettingsNode = $NavHierNode.SelectSingleNode(“FolderHierarchy”)
I’ve created Folder Content Types, by inheriting from the Folder Content Type.  This approach allows Folders to hold useful metadata and be selectable by users on creation.  In this case, I use the Folder Content Type to determine the View.  You can make the choice instead on the folder URL or metadata.  I simply loop through all folders, and select the preferred default folder based on the location:

for ($i=0; $i -lt $FolderCount; $i++)
{
 $Folder=$Folders[$i]
 switch ($Folder.ContentType.name)
 {
  "Underwriting Folder"  { $DefaultViewName= "UW View"}
  "Claim Folder"   { $DefaultViewName= "Claim View"}
  "Policy Folder"  { $DefaultViewName= "UW View"}
  "Loss Control Folder" { $DefaultViewName= "LC View"}
  default   { $DefaultViewName= "All Documents"}
 }

I grab the actual view, but take care to fall back on All Documents if the View is not found:

$View=$Views[$DefaultViewName];
 if ($View -eq $null)
 {
  Write-Host "View ($($DefaultViewName) not found, falling back to All Documents"
  $DefaultViewName= "All Documents"; # logic assumes this is always there
  $View=$Views[$DefaultViewName];
 }
</sourcecode>
Now, we want to make note of the folder details for use in building the XML.  As you recall there are three aspects to the View of interest: the GUID, ID and relative URL:
$FolderGuid=$Folder.UniqueId
 $FolderID= $Folder.id
 $FolderURL= $Folder.Url;</pre>
So let's start with the folder entry in the XML, by creating a new folder node, and populating it's attributes, and first element, for the default view:
<pre> $NewFolderNode=$X.CreateElement("ViewSettings");
 $NewFolderNode.SetAttribute("UniqueNodeId",$FolderGuid);
 $NewFolderNode.SetAttribute("FolderId",$FolderID);
 $NewViewNode=$X.CreateElement("View");
 $NewViewNode.SetAttribute("ViewId",$View.id);
 $NewViewNode.SetAttribute("CachedName",$View.Title);
 $NewViewNode.SetAttribute("Index","0");  #0 forces view to be default
 $NewViewNode.SetAttribute("CachedUrl",$View.Url);
 $NewFolderNode.AppendChild($NewViewNode);</pre>
Finally, we can add all the other Views for the folder.  Note we start the index from 1, after the 0 default view.  Now's where the array of Views created earlier comes into use.  I always like starting with a comma separated list, converting these into an array for easy use.  We make sure not to add the default View a second time:
<pre> $Index=1; # we want in increment index for each view for sequence
 foreach ($ViewName in $ViewArr)
 {
  $View=$Views[$DefaultViewName];
  if ($View -eq $null)
  {
   Write-Host "View ($($DefaultViewName) not found, secondary view, skipping" #never do a continue within foreach!
  }
  elseif ($ViewName -ne $DefaultViewName) #make sure to skip adding the default view as a secondary view
  {
   $View=$Views[$ViewName];
   $NewViewNode=$X.CreateElement("View");
   $NewViewNode.SetAttribute("ViewId",$View.id);
   $NewViewNode.SetAttribute("CachedName",$View.Title);
   $NewViewNode.SetAttribute("Index",$Index.tostring());  #0 forces view to be default
   $NewViewNode.SetAttribute("CachedUrl",$View.Url);
   $NewFolderNode.AppendChild($NewViewNode);
   $Index++; #view sequence numbering
  }
 }
 $ViewSettingsNode.AppendChild($NewFolderNode) </pre>

Finally, we save our XML.  Note both update() are required:

$RF.Properties["client_MOSS_MetadataNavigationSettings"]=$x.InnerXml.ToString();
$RF.Update()
$list.Update() #both property and List update are required</pre>
[/sourcecode ]
See: <a href="http://technet.microsoft.com/en-us/library/microsoft.office.documentmanagement.metadatanavigation.metadatanavigationsettings.aspx">Technet reference</a>
Let's finally put it all together:
$web=get-spweb href="http ://sharepoint/MySite"
$lists=$web.lists
$list=$lists["MyListName"]
$Folders=$list.Folders
$ViewSet="All Documents,EMail,Claim View,LC View,UW View"
$ViewArr=$ViewSet.split(",")
$WipeFolderDefaults=$true;  # This is optional
$Views=$list.Views 
$RF=$List.RootFolder
#$x.get_Properties()
$x=$RF.Properties["client_MOSS_MetadataNavigationSettings"]
if ($WipeFolderDefaults)
{
                try #if it fails, it is because the node just isn't there, which might mean no folders defined, which is fine.
                {
                                $x.MetadataNavigationSettings.NavigationHierarchies.FolderHierarchy.removeall()  
                }
                catch
                {
                                write-host "nothing to wipe, we are good to go!"
                }
}
$FolderCount=$Folders.count;
$NavHierNode = $x.MetadataNavigationSettings.NavigationHierarchies
$ViewSettingsNode = $NavHierNode.SelectSingleNode("FolderHierarchy") #grabs it as XMLNode, instead of string, if empty node 
for ($i=0; $i -lt $FolderCount; $i++)
{
                $Folder=$Folders[$i]
                switch ($Folder.ContentType.name)
                {
                                "Underwriting Folder"            { $DefaultViewName= "UW View"}
                                "Claim Folder"                   { $DefaultViewName= "Claim View"}
                                "Policy Folder"                  { $DefaultViewName= "UW View"}
                                "Loss Control Folder"            { $DefaultViewName= "LC View"}
                                default                         { $DefaultViewName= "All Documents"}
                }
                $View=$Views[$DefaultViewName];
                if ($View -eq $null)
                {
                                Write-Host "View ($($DefaultViewName) not found, falling back to All Documents"
                                $DefaultViewName= "All Documents"; # logic assumes this is always there
                                $View=$Views[$DefaultViewName];
                }
                $FolderGuid=$Folder.UniqueId
                $FolderID= $Folder.id
                $FolderURL= $Folder.Url;
                $NewFolderNode=$X.CreateElement("ViewSettings");
                $NewFolderNode.SetAttribute("UniqueNodeId",$FolderGuid);
                $NewFolderNode.SetAttribute("FolderId",$FolderID);
                $NewViewNode=$X.CreateElement("View");
                $NewViewNode.SetAttribute("ViewId",$View.id);
                $NewViewNode.SetAttribute("CachedName",$View.Title);
                $NewViewNode.SetAttribute("Index","0");  #0 forces view to be default
                $NewViewNode.SetAttribute("CachedUrl",$View.Url);
                $NewFolderNode.AppendChild($NewViewNode);
                $Index=1; # we want in increment index for each view for sequence
                foreach ($ViewName in $ViewArr)
                {
                                $View=$Views[$DefaultViewName];
                                if ($View -eq $null)
                                {
                                                Write-Host "View ($($DefaultViewName) not found, secondary view, skipping" #never do a continue within foreach!
                                }
                                elseif ($ViewName -ne $DefaultViewName) #make sure to skip adding the default view as a secondary view
                                {
                                                $View=$Views[$ViewName];
                                                $NewViewNode=$X.CreateElement("View");
                                                $NewViewNode.SetAttribute("ViewId",$View.id);
                                                $NewViewNode.SetAttribute("CachedName",$View.Title);
                                                $NewViewNode.SetAttribute("Index",$Index.tostring());  #0 forces view to be default
                                                $NewViewNode.SetAttribute("CachedUrl",$View.Url);
                                                $NewFolderNode.AppendChild($NewViewNode);
                                                $Index++; #view sequence numbering
                                }
                }
                $ViewSettingsNode.AppendChild($NewFolderNode)  
                }
#$x.InnerXml &gt; "L:PowerShellPer Location Views2a.xml"
$RF.Properties["client_MOSS_MetadataNavigationSettings"]=$x.InnerXml.ToString();
$RF.Update()
$list.Update() #both property and List update are required
0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *