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.

30 comments:

  1. Excellent stuff, just what I was looking for. I had to extend it somewhat to support things like external links and importing into a root site, but you did all the hard work!

    One funny thing I noted: $web.Navigation.QuickLaunch returned null when I had NOT installed the publishing feature. After installing the feature on the site collection, it all worked beautifully. I thought the opposite would be the case...

    ReplyDelete
  2. Hi Svetho,

    Not sure why you had to extend it to support external links as the examples I used here have included them. Would be interested to know (perhaps you could share your revised version?).

    Interesting that you got null without the publishing feature activated. I had two site collections running - one with the feature activated and one without. This script was written on the one without and I have a script coming soon on the one I did with it activated (where I did have to also specify external links).

    I'll test it again. Thanks for the comments.

    ReplyDelete
  3. Hi Phil,
    thank you for the feedback.

    Here are links to the updated scripts:
    http://dl.dropbox.com/u/7751619/quick-launch-export.ps1
    http://dl.dropbox.com/u/7751619/quick-launch-import.ps1

    Looking forward to your next posting! :-)

    ReplyDelete
  4. Thanks for sharing Svetho - nice changes, btw.

    I will go back and re-test the publishing infrastructure feature activated/deactivated scenarios to make sure I'm not going mad!

    ReplyDelete
  5. Svetho - For info, I did try again with two new site collections and got the same results. The script in this article worked on the site collection without the Publishing Infrastructure feature enabled but didn't on the site collection with the feature enabled - I have a script lined up for a new article on this scenario coming soon...

    ReplyDelete
  6. Hi i have the following script.

    $web = Get-SPWeb http://intranet/sites/hr
    $qlNav = $web.Navigation.QuickLaunch
    $qlHeading = $qlNav | where { $_.Title -eq "Reports" }
    $linkNode = New-Object Microsoft.SharePoint.Navigation.SPNavigationNode("Report", "SitePages/Rapport_02.aspx", $true)
    $qlHeading.Children.AddAsLast($linkNode)

    I want to add this item to a collection of sites can you help me out with this i have a list with all sites that must have this navigation item

    ReplyDelete
  7. Hi,

    thanks for the post!
    I have a question for you..
    Is it possible to set a relative path for $xmlFilePath instead of absolute path?
    Thanks again!

    ReplyDelete
  8. THis is good stuff, Question: How do I set the Global Navigation's display the same navigation items as the parent site settings.

    ReplyDelete
  9. Hi,

    nice post! Based on this I tried to update quicklaunch item title correctly in Powershell but GUI does not reflect the change and I don't know what is going wrong.

    $url = "http://webapp/sitecollection/website"
    $web = Get-SPWeb $url
    $qlNav = $web.Navigation.QuickLaunch
    $qlHeading = $qlNav | where { $_.Title -eq "OldTitle" }
    $qlHeading.Title = "NewTitle"
    $qlHeading.Update()
    $web.Update()
    $web.Dispose()
    & "C:\Program Files\Internet Explorer\iexplore.exe" $url

    Now IE show the OldTitle but querying it in Powershell shows NewTitle.

    If I call also $qlNav.Update() then I get
    Method invocation failed because [Microsoft.SharePoint.Navigation.SPNavigationNodeCollection] doesn't contain a method named 'Update'.
    although the method is there...
    SPNavigationNode.Update Method (Microsoft.SharePoint.Navigation)
    http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.navigation.spnavigationnode.update.aspx

    Do you know why are these problems happening?

    ReplyDelete
  10. Hi, nice job. I had two site collections running - one with the feature activated and one without. thanks for sharing.
    Forex Software

    ReplyDelete
  11. Thank You for sharing.

    Was looking for a nice way to setup some menu items using PowerShell

    ReplyDelete
  12. Phil,
    I just noticed something odd - after using PowerShell to edit the current navigation on my site, the site highlighting is no longer accurate. It now highlights the parent site instead. If you use the UI to go into the navigation edit screen, simply click ok to save and exit, then everything highlights again normally.

    Any thoughts on why this may be happening or how to fix it?

    Thanks,
    Brian

    ReplyDelete
  13. Brian - not sure. Could you post the navigation inheritance settings and whether this is a SharePoint Foundation or Server site?

    ReplyDelete
  14. Thank you a lot for this !

    But one question remains ... I know how to create a document library, and how to create a title ...

    But how the hell ! can i create a document library, under a specific title ?

    Save me :)

    Thanks,
    Nico

    ReplyDelete
  15. Hi Phil,

    one problem I can't resolve. I try to add a Link with Parameters like
    /departments/Pages/default.aspx?Title='Marketing'&Language='EN'
    But the link will be cut before the ? and the result is just
    /departments/Pages/default.aspx
    I also tried to escape the ? with ` but it did not work.
    regards
    Falk

    ReplyDelete
    Replies
    1. Hi gain,

      I got it, you have to use the property for a Link (just heading don't work)
      $linkNode.Properties["NodeType"] = "AuthoredLinkPlain"
      $linkNode.Update()

      Delete
  16. This comment has been removed by the author.

    ReplyDelete
  17. I have an issue with the import part of the process... If I create an XML with only Headers, and no link, the script still creates (invisible) links. How can I stop this?

    ReplyDelete
  18. This blog is so nice to me. I will continue to come here again and again. Visit my link as well. Good luck
    obat aborsi
    cara menggugurkan kandungan
    obat telat datang bulan
    obat penggugur kandungan

    ReplyDelete
  19. The Nike LeBron Soldier 10 Christ the King is a brand new Nike LeBron Soldier 10 that pays homage to the top NYC basketball KD 6 school, Christ the King high school.LeBron James has supported the high school for years now. Dressed in a Team Red, LeBron 14 Metallic Gold and Off-White color scheme. This release is perfect for those that either support the school or are just Basketball Shoes die-hard Cleveland Cavaliers fans. Featuring a Team Red-based upper with Metallic Gold accents throughout that sits atop an Off-White rubbers Kobe 11 sole.Look for the Nike LeBron Soldier 10 “Christ the King” to release on Saturday, October 15th at select Nike Basketball Curry 3 USA retail stores. The retail price tag is set at $130 USD.
    The Oregon Ducks basketball squad will be rocking a Kobe venomenon 5 special edition Nike KD 9 PE for Veteran’s Day.This Oregon Ducks Nike KD 9 PE comes dressed in a Black, NBA Store Green and Yellow color scheme that includes feather print detailing and sitting atop a clear translucent outsole.To go along with Cavalier Steam Shop their exclusive Nike KD 9, the team will also be rocking their special edition digital camo “Fighting Ducks” uniform.Check out Warriors Team Store the uniforms and KD 9s below and look for the Oregon Ducks in the Veteran’s Day getup this Friday against KD shoes Army.
    Jordan Brand will be releasing an Air Jordan 9 Retro that pays homage to Kobe Bryant — although, we’re NBA Stars Shoes not sure if that is the official story they’ll be running with.These are scheduled to release on November 19 for Kobe 8 $190, and are nearly identical to the pair that was made for Kobe Bryant while he and Shaq were teammates. kobe 10 elite They actually had matching pairs, only difference being they each had their jersey numbers listed on the heel of the Kobe shoes shoe. Kobe wore 8 back then, for those that don’t remember.When we mention that they’re nearly identical it’s because they KD 7 went ahead and changed the 8 to a 23. The right outsole also has the backwards Jumpman — not exactly Kobe 9 remastered either.Check out the official images below and let us know if you plan on grabbing a pair.

    ReplyDelete