Wednesday, 23 March 2011

Managing Quick Launch navigation in SharePoint Foundation using PowerShell

Quick Launch (left-hand navigation) functionality in SharePoint Foundation can be fairly limited if you’re looking to add or standardise links across multiple sites. Unlike SharePoint Server 2010, there are no facilities available to inherit navigation items from parent sites or show items below the current site.

One method of managing navigation items on sites without reverting to code-based solutions would be to use PowerShell. In this article I will run through the basics of moving and creating new Quick Launch headings and links, and at the end provide a couple of scripts that allow you to export Quick Launch items from one site and import them into another site.

Note that these scripts will only work on SharePoint Foundation sites – or sites in SharePoint Server 2010 where the “SharePoint Server Publishing Infrastructure” site collection feature is not activated. I have covered managing SharePoint Server 2010 Quick Launch navigation on site collections with the Publishing Infrastructure feature activated in this article.

First, we need to get the Web and QuickLaunch objects and assign them to variables. For this example, I am modifying the root site of a site collection created at http://portal/sites/foundation:

$web = Get-SPWeb http://portal/sites/foundation
$qlNav = $web.Navigation.QuickLaunch

If you would like to see a list of headings already created on the site with their node ID’s, type the following:

$qlNav | select Title, ID

qlheadingsbeforechanges

To make changes to a heading, we need to assign it to a variable first. There are methods to do this with the ID and link URL, but I prefer to use the heading Title as it’s more user friendly and will be the same across each site. For example, type this command to get the Libraries heading:

$qlHeading = $qlNav | where { $_.Title -eq "Libraries" }

If there is more than one heading with the same name, you will need to type the following command to select the correct one. Changing the number in the square brackets will be how you do this, with 0 being the first heading, 1 the second heading, 2 the third heading, etc.

$qlHeading = (qlNav | where { $_.Title -eq "Libraries" })[0]

If you are looking to move the heading order, then you need to get the heading you wish to move, the heading that you wish it to appear under, and then apply the Move method. For example, to move the Lists heading to appear underneath the Discussions heading, type the following:

$qlHeading = $qlNav | where { $_.Title -eq "Lists" }
$qlNewPreviousSibling = $qlNav | where { $_.Title -eq "Discussions" }
$qlHeading.Move($qlNav, $qlNewPreviousSibling)

qlheadingsaftermove

To create a new heading, we have to use the SPNavigationNode class, which accepts the following properties:

  • Display Name (required) – Will appear in the Quick Launch as the name of the item
  • URL (required) – Hyperlink address for the item. You can set this with double-quotes if you do not want to set a URL for the item
  • External Link [$true/$false] (optional) – Specifies whether the link is internal (item in SharePoint) or external (another website). As it is set to $false by default, you only need to specify this property if creating a link to an external site.

The following example creates a heading under Libraries called “External Links”, which will appear in the Quick Launch but does not actually link to anywhere:

$qlNewPreviousSibling = $qlNav | where { $_.Title -eq "Libraries" }
$headingNode = New-Object Microsoft.SharePoint.Navigation.SPNavigationNode("External Links", "")
$qlNav.Add($headingNode, $qlNewPreviousSibling)

qlheadingsafteradd

To add links to a heading, we use the same SPNavigationNode class as before, with the same properties. First, we get the heading that the links will appear under and then add them to the heading as children using the Add, AddAsFirst, or AddAsLast methods. In the example below I add one internal and two external links to our new External Links heading:

$qlHeading = $qlNav | where { $_.Title -eq "External Links" }
$linkNode = New-Object Microsoft.SharePoint.Navigation.SPNavigationNode("Get-SPScripts", "
http://get-spscripts.com", $true)
$qlHeading.Children.AddAsLast($linkNode)
$linkNode = New-Object Microsoft.SharePoint.Navigation.SPNavigationNode("Links", "/sites/foundation/Lists/Links/AllItems.aspx")
$qlHeading.Children.AddAsLast($linkNode)
$linkNode = New-Object Microsoft.SharePoint.Navigation.SPNavigationNode("Follow me on Twitter", "
http://twitter.com/phillipchilds", $true)
$qlHeading.Children.AddAsLast($linkNode)

To view a list of these links after creation, type the following command:

$qlHeading.Children | select Title, ID

qlnewlinks

Finally, to move the link order, you need to get the link you wish to move, the link that you wish it to appear under, and then apply the Move method. The link can be assigned to a variable by accessing the Children property of the heading. For example, to move the “Follow me on Twitter” link to appear underneath the “Get-SPScripts” link, type the following:

$qlNewPreviousSibling = $qlHeading.Children | where { $_.Title -eq "Get-SPScripts" }
$qlLink = $qlHeading.Children | where { $_.Title -eq "Follow me on Twitter" }
$qlLink.Move($qlHeading.Children, $qlNewPreviousSibling)

qllinksaftermove

The end result of all this scripting is that we now have a new heading and three links underneath that heading:

EndResult

Exporting/Importing Quick Launch Navigation Items

As promised, the following scripts will provide the ability for you to export the Quick Launch navigation items from one site and import them into another site. The export script actually creates an XML file on the computer with the items listed hierarchically. The advantage with this approach is that you could create the XML file manually for import into sites as a new structure.

Here is the export script:

#Get Web and Quick Launch objects
$web = Get-SPWeb http://portal/sites/foundation
$qlNav = $web.Navigation.QuickLaunch

#Create Export File
$xmlFilePath = "C:\Install\QuickLaunchNav.xml"
New-Item $xmlFilePath -type file -force

#Export Quick Launch Navigation to XML file
Add-Content $xmlFilePath "<?xml version=`"1.0`" encoding=`"utf-8`"?>"
Add-Content $xmlFilePath "`n<Navigation>"
$webUrlXml = "`n<WebUrl>" + $web.ServerRelativeUrl + "</WebUrl>"
Add-Content $xmlFilePath $WebUrlXml
Add-Content $xmlFilePath "`n<Headings>"
$qlNav | ForEach-Object {
    $headingXML = "`n<Heading Title=`"" + $_.Title.Replace("&","&amp;") + "`" Url=`"" + $_.Url.Replace("&","&amp;") + "`">"
    Add-Content $xmlFilePath $headingXML
    $_.Children | ForEach-Object {
        $navLinkXML = "<NavLink Title=`"" + $_.Title.Replace("&","&amp;") + "`" Url=`"" + $_.Url.Replace("&","&amp;") + "`"/>"
        Add-Content $xmlFilePath $navLinkXML
    }
    Add-Content $xmlFilePath "`n</Heading>"
}
Add-Content $xmlFilePath "`n</Headings>"
Add-Content $xmlFilePath "`n</Navigation>"
$web.Dispose()

This script will look at the site specified and create an XML file similar to the one shown below:

image

One thing to note here is that we store the URL of the originating site in a <WebUrl> property. We need this when importing navigation items into other sites as we will replace the site path stored in the XML file with the site path of the destination site for any internal links – for example, if the destination site path is /sites/newsite, in the example above we will replace /sites/foundation with /sites/newsite for all internal links.

Now the import script:

#Get Web and Quick Launch objects
$web = Get-SPWeb
http://portal/sites/newsite
$qlNav = $web.Navigation.QuickLaunch

#Get XML File
$xmlFilePath = "C:\Install\QuickLaunchNav.xml"
$xmlFile = [xml](Get-Content($xmlFilePath))
$oldWebUrl = $xmlFile.Navigation.WebUrl
$currentLinks = @()

#Clear Quick Launch links
$qlNav | ForEach-Object {
    $currentLinks = $currentLinks + $_.Id
}
$currentLinks | ForEach-Object {
    $currentNode = $web.Navigation.GetNodeById($_)
    write-host "Deleting" $currentNode.Title "and all child navigation links..."
    $qlNav.Delete($currentNode)
}

#Create Quick Launch Links
$xmlFile.Navigation.Headings.Heading | ForEach-Object {
    $headingNode = New-Object Microsoft.SharePoint.Navigation.SPNavigationNode($_.Title, $_.Url.Replace($oldWebUrl, $web.ServerRelativeUrl))
    write-host "Creating Heading:" $_.Title
    $heading = $qlNav.AddAsLast($headingNode)
    $_.NavLink | ForEach-Object {
        $linkNode = New-Object Microsoft.SharePoint.Navigation.SPNavigationNode($_.Title, $_.Url.Replace($oldWebUrl, $web.ServerRelativeUrl))
        write-host "Creating Navigation Link:" $_.Title
        $link = $heading.Children.AddAsLast($linkNode)
    }
}
$web.Dispose()

This script connects to the destination site, reads the XML file, clears the navigation items on the site currently, and creates the Quick Launch navigation items and links as defined in the XML file. Note that the script will wipe any navigation items on the destination site before creating these items – it will not add new items to those already present (although you could alter the script to do so). Therefore, make sure that you test, test and test again before trying this out on a live environment.

Wednesday, 9 March 2011

Using PowerShell scripts with WSS 3.0 and SharePoint 2007

Although Windows SharePoint Services 3.0 and SharePoint Server 2007 do not include built-in PowerShell cmdlets, this does not stop you using PowerShell to manage these servers and content where the supplied STSADM commands fall short in functionality or flexibility.

You can install Windows PowerShell 2.0 (which includes the ISE Windows application) from any Windows 2003 or 2008 server by downloading it from Windows Update. Once you have the console started, simply load the SharePoint assemblies with the following command before you start scripting:

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")

If you want to load this command every time you launch the PowerShell console or ISE application, add it to a file called profile.ps1 and copy this file into a folder called WindowsPowerShell in the My Documents folder for that user. For an ISE-specific profile, copy the command into a file called Microsoft.PowerShellISE_profile.ps1 in the same folder.

You will also need to set the execution policy on the server to RemoteSigned if you want to be able to run PS1 scripts locally. This can be set with the following command:

Set-ExecutionPolicy RemoteSigned

Once set, the configuration will remain fixed on the server unless it is reset by an administrator or through Group Policy. Type get-help about_Execution_Policies at a PowerShell command line for more information on this.

I find the majority of scripts used to manage a SharePoint 2010 environment involve use of the Get-SPSite and Get-SPWeb cmdlets to call a site collection or site respectively. Although we do not have these cmdlets available to us in SharePoint 2007, there are alternative ways of retrieving these objects.

To assign a site collection object to a variable, use the following command, replacing http://portal with the URL of your site collection:

$site = New-Object Microsoft.SharePoint.SPSite(“http://portal”)

To open the root site of the site collection and assign it to a variable, type the following:

$web = $site.RootWeb

If you need access to a specific site in a site collection, you can specify the relative path to the site with the OpenWeb method. For example, to get a site at the URL http://portal/subsite1/subsite2, type the following command:

$web = $site.OpenWeb(“subsite1/subsite2”)

Once you have assigned these objects to variables, you can use them in the same way as with SharePoint 2010. For example, to change the title of a site, type the following commands:

$web.Title = "New Title"
$web.Update()

Also bear in mind that unless the commands you are using have been deprecated in the object model or you are trying to access functionality that no longer exists, most PowerShell scripts written for SharePoint 2007 will work with SharePoint 2010. Therefore, if you are managing both environments, you may find it a more supportable option to write your scripts for SharePoint 2007 first and then test for 2010 compatibility.

The scripts I publish on this site are generally compatible with SharePoint 2010 only, so to help you translate these scripts into SharePoint 2007 versions, here are a few useful SharePoint 2010 cmdlet alternatives:

Description SharePoint 2010 SharePoint 2007
Assign a Web Application object to the $webApp variable $webApp = Get-SPWebApplication http://portal $webapp = [Microsoft.SharePoint.Administration.SPWebApplication]::Lookup(“http://portal”)
Assign the AD account “DOMAIN\SP_Admin” to the $user variable $user = Get-SPUser -Web $web -User "DOMAIN\SP_Admin" $user = $web.EnsureUser("DOMAIN\SP_Admin")
Assign the local SharePoint Farm object to the $farm variable $farm = Get-SPFarm $farm = [Microsoft.SharePoint.Administration.SPFarm]::Local
Get a named feature from the SharePoint Farm and assign it to the $feature variable

$feature = Get-SPFeature "FeatureName"

$feature = $farm.FeatureDefinitions["FeatureName"]
Activate a site-scoped feature on a site collection

Enable-SPFeature $feature -Url $site.Url

$site.Features.Add($feature.ID)
Deactivate a site-scoped feature on a site collection Disable-SPFeature $feature -Url $site.Url -Force $site.Features.Remove($feature.ID)
Use this command when making a call to a service application (2010) or shared service (2007). For example, it is used when gaining access to the User Profile Manager object model for managing user profiles in a script

$context = Get-SPServiceContext $site

$context = [Microsoft.Office.Server.ServerContext]::GetContext($site)

I will update this table above over time to include any other SharePoint 2007 alternatives to SharePoint 2010 cmdlets as I come across them.

Monday, 7 March 2011

Useful PowerShell for SharePoint links and resources #3

I haven’t posted one of these for a while, but there are quite a few great PowerShell resources amongst the SharePoint community, and I like to list a few of my favourite links on this site now and again so that I do not forget them.

Thursday, 3 March 2011

Configuring metadata navigation settings on SharePoint lists with PowerShell

A nice feature in SharePoint Server 2010 is the ability to provide list and document library navigation/filtering through metadata and content type association. In a nutshell, this provides the ability to switch on features in the SharePoint user interface that allow users to find information by filtering and navigating using metadata tags and content types as opposed to folders, which as we all know have their limitations. Further details on the feature can be found here.

The feature is configured in the user interface by selecting “Metadata navigation settings” from the list settings. This will display an administration interface similar to the one shown below:

image

As you can see from the screenshot above, I have two custom columns called Document Status (single-value Choice type) and Technology (single-value Managed Metadata type) available in the Available Hierarchy Fields and Available Key Filter Fields boxes ready to add as a navigation hierarchy or key filter. Note the default setting for Selected Hierarchy Fields is Folders.

For this example, I will use PowerShell to configure both the navigation hierarchy and key filters settings to include my custom columns. I will also show you how to add the Content Type and Folders options too.

First, we will assign the site that contains the list to a variable and use this variable to get the list:

#Get Web and List objects
$web = Get-SPWeb
http://portal/permuniquesite
$list = $web.Lists["Shared Documents"]

Next, we will get the current metadata navigation settings for the list and assign them to a variable:

#Get metadata navigation settings for the list
$listNavSettings = [Microsoft.Office.DocumentManagement.MetadataNavigation.MetadataNavigationSettings]::GetMetadataNavigationSettings($list)

The next two lines of the script clear the configured hierarchies and configured key filters on the list. You could use these commands in combination with the SetMetadataNavigationSettings and $list.RootFolder.Update() commands mentioned later to remove any currently configured metadata navigation settings for the list:

#Clear current metadata navigation settings on the list
$listNavSettings.ClearConfiguredHierarchies()
$listNavSettings.ClearConfiguredKeyFilters()

These commands will add my two custom columns to the Selected Key Filter Fields:

#Configure key filters by adding columns
$listNavSettings.AddConfiguredKeyFilter($list.Fields["Technology"])
$listNavSettings.AddConfiguredKeyFilter($list.Fields["Document Status"])

These next two lines are quite important. I have found that the navigation hierarchy configuration does not work when configuring it from PowerShell unless you add the default Folders item to the Selected Hierarchy Fields first. Without this step, the custom columns will be added successfully in the Metadata navigation settings page, but they will not appear in the Tree View interface:

#Add folder navigation hierarchi$listes into list settings
#This is required to enable and show navigation hierarchies in the Tree View
$folderHierarchy = [Microsoft.Office.DocumentManagement.MetadataNavigation.MetadataNavigationHierarchy]::CreateFolderHierarchy()
$listnavSettings.AddConfiguredHierarchy($folderHierarchy)

The next step is optional, but contains the commands needed to add Content Type navigation hierarchies to the settings, if required:

#Optionally add content type navigation hierarchies into list settings if required
$ctHierarchy = [Microsoft.Office.DocumentManagement.MetadataNavigation.MetadataNavigationHierarchy]::CreateContentTypeHierarchy()
$listnavSettings.AddConfiguredHierarchy($ctHierarchy)

We now add the custom columns to the Selected Hierarchy Fields:

#Configure extra navigation hierarchies by adding list columns
$listNavSettings.AddConfiguredHierarchy($list.Fields["Technology"])
$listNavSettings.AddConfiguredHierarchy($list.Fields["Document Status"])

And finally, set the metadata navigation settings and update the root folder of the list, which is where the settings are stored:

#Set the new metadata navigation settings and update the root folder of the list
[Microsoft.Office.DocumentManagement.MetadataNavigation.MetadataNavigationSettings]::SetMetadataNavigationSettings($list, $listNavSettings, $true)
$list.RootFolder.Update()

The Metadata navigation settings page should now look as follows:

image

To test the metadata navigation feature, you will need to switch on the Tree View setting for the site. This can be done in the UI by selecting Site Settings > Tree view > Enable Tree View or you can change it with a couple of extra lines in your PowerShell script:

#Enable Tree View on the site so that navigation hierarchies can be used in the UI
$web.TreeViewEnabled = $true
$web.Update()

image

The metadata navigation and key filters features will show up on the left-hand side of the SharePoint user interface, underneath the quick launch menu, as shown above. To complete the script, don’t forget to dispose of the Web object:

#Dispose of the Web object
$web.Dispose()

Wednesday, 2 March 2011

Running PowerShell PS1 script files in the SharePoint Management Shell

I have had a few comments and e-mails recently from people having problems running the PowerShell scripts published on this site as PS1 files in the Management Shell console application included with SharePoint 2010. Unfortunately, PS1 files do not run like batch (.BAT) files used to by simply typing the name of the file.

Personally, I prefer to use Windows PowerShell ISE that ships with PowerShell 2.0 or Windows 7/Server 2008 R2 for developing and running scripts (see here for details on how to use this with SharePoint 2010), but for those that wish to use the Management Shell, launch it from the start menu and type the following syntax to run a script file:

 . ‘LocalPath\ScriptName.PS1’

….and hit the return key. To clarify, that is [dot][space][script name in single quotes]. The quotes allow you to use spaces in your script names, if desired.

The script will either run there and then, or if it is written as a function, you can then call the function using its name and any associated switches. For example, here a screenshot to demonstrate the function that I posted here to check the size of a site hierarchy in SharePoint 2010:

image

Note that the first line loads the function from a PS1 file and the second line kicks off the script by calling the GetWebSizes function with the StartWeb switch.