CHUVASH.eu

CHunky Universe of Vigourous Astonishing SHarepoint :)

Tag Archives: SPWeb

PowerShell: Copy an entire document library from SharePoint 2007 to disk

For a while ago I needed to copy all files from a document library within a SharePoint 2007 site to the hard drive. So I didn’t need to copy files from SharePoint to SharePoint so I couldn’t use the stsadm -o export command or Chris O’Brien’s nice SharePoint Content Deployment Wizard. I came across the SPIEFolder application which should work with SharePoint 2007 and 2010. It has a site on codeplex: spiefolder.codeplex.com, but neither the binary nor the source code can be downloaded from there. After some searching I found the binary in the author’s skydrive. The fact that the source code was not available seemed as an disanvantage because I could not know what code was run. Nevertheless I tried it out and it didn’t work:

spiefolder -o export -url "http://dev/Documents" -directory c:\tolle\Documents –recursive

I got the following error:

The Web application at http://dev/Documents could not be found. Verify that you have typed the URL correctly. If the URL should be serving existing content, the system administrator may need to add a new request URL mapping to the intended application.

So I wrote my own code to copy the documents. To write a console application feels so yesterdayish, so it is written in PowerShell. Even if there are no PowerShell snapins for SharePoint 2007, you have access to the entire Server Object Model, the only thing you have to do is to load the SharePoint assembly:

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

Then you can instantiate all SharePoint objects like in C#, but in a PowerShell way:

$site = new-Object Microsoft.SharePoint.SPSite("http://dev")
$web = $site.OpenWeb()

You can even download a module for emulating cmdlets: Get-SPWeb, Get-SPWebApplication and Get-SPFarm, written by Natalia Tsymbalenko (sharing-the-experience.blogspot.com) to get started or just to find some inspiration.

I have created a ps1-script which only does one thing – it copies an entire document library to disk. Much of inspiration to structure the script comes from “Delete-SPListItems” (sharepointryan.com).

Here it is: Pull-Documents.ps1

<#
.Synopsis
    Use Pull-Documents to copy the entire document library to disk
.Description
    This script iterates recursively over all directories and files in a document library and writes binary data to the disk
    The structure is kept as in the Document library
    It is mainly written for SharePoint 2007, but it works even in SharePoint 2010
.Example
    Pull-Document -Url http://dev -Library "Shared Documents"
.Notes
    Name: Pull-Documents.ps1
    Author: Anatoly Mironov
    Last Edit: 2012-12-03
    Keywords: SPList, Documents, Files, SPDocumentLibrary
.Links
    https://sharepointkunskap.wordpress.com
    http://www.bool.se
.Inputs
    None
.Outputs
    None
#Requires -Version 1.0
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)][System.String]$Url = $(Read-Host -prompt "Web Url"),
[Parameter(Mandatory=$true)][System.String]$Library = $(Read-Host -prompt "Document Library")
)
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")

$site = new-object microsoft.sharepoint.spsite($Url)
$web = $site.OpenWeb()
$site.Dispose()

$folder = $web.GetFolder($Library)
$folder # must output it otherwise "doesn't exist" in 2007

if(!$folder.Exists){
    Write-Error "The document library cannot be found"
    $web.Dispose()
    return
}

$directory = $pwd.Path

$rootDirectory = Join-Path $pwd $folder.Name

if (Test-Path $rootDirectory) {
    Write-Error "The folder $Library in the current directory already exists, please remove it"
    $web.Dispose()
    return
}

#progress variables
$global:counter = 0
$global:total = 0
#recursively count all files to pull
function count($folder) {
    if ($folder.Name -ne "Forms") {
        $global:total += $folder.Files.Count
        $folder.SubFolders | Foreach { count $_ }
    }
}
write "counting files, please wait..."
count $folder
write "files count $global:total"

function progress($path) {
    $global:counter++
    $percent = $global:counter / $global:total * 100
    write-progress -activity "Pulling documents from $Library" -status $path -PercentComplete $percent
}

#Write file to disk
function Save ($file, $directory) {
    $data = $file.OpenBinary()
    $path = Join-Path $directory $file.Name
    progress $path
    [System.IO.File]::WriteAllBytes($path, $data)
}

#Forms folder doesn't need to be copied
$formsDirectory = Join-Path $rootDirectory "Forms"

function Pull($folder, [string]$directory) {
    $directory = Join-Path $directory $folder.Name
    if ($directory -eq $formsDirectory) {
        return
    }
    mkdir $directory | out-null

    $folder.Files | Foreach { Save $_ $directory }

    $folder.Subfolders | Foreach { Pull $_ $directory }
}

Write "Copying files recursively"
Pull $folder $directory

$web.Dispose()

I have tested this script in SharePoint 2007 and 2010. It works. Let me know if you find this useful or have some suggestions.

Add global navigation links in Powershell and Feature Receiver

I think, powershell is the best way to do configurations you have to do once. Adding some links to global (top) navigation is one of them:

asnp microsoft.sharepoint.powershell
$w = get-spweb http://takana
$l = New-Object Microsoft.SharePoint.Navigation.SPNavigationNode("Smells like team spirit", "/pages/teamspirit.aspx")
$w.Navigation.TopNavigationBar.AddAsLast($l)
Feature receiver

The alternative is to create a web scoped feature and provide properties:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    var web = properties.Feature.Parent as SPWeb;
    var prop = properties.Feature.Properties["MyGlobalLinks"];
    var links = prop.Value.Split(new[] { ";#" },
               StringSplitOptions.RemoveEmptyEntries);
    foreach (var item in links)
    {
        var newLink = item.Split(new[] { ";" },
                            StringSplitOptions.RemoveEmptyEntries);
        var newMenuItem =
                new SPNavigationNode(newLink[0], newLink[1]);
        web.Navigation.TopNavigationBar.AddAsLast(newMenuItem);
    }
}

This feature can be prefereably hidden. The properties are passed in onet:

<!--Meglerfront: Page Navigation-->
<Feature ID="7829235a-ffb3-4ddf-0b5b-2a1d79668aa5">
  <Properties xmlns="http://schemas.microsoft.com/sharepoint/">
    <Property Key="MyGlobalLinks"
     Value="Salam;/$Resources:cmscore,List_Pages_UrlName;/salam.aspx;#Ciper;/$Resources:cmscore,List_Pages_UrlName;/ciper.aspx" />
  </Properties>
</Feature>

In the example no error handling is provided for the sake of brevity.

NavBarLink

I found even a third way to add links to global navigation: NavBarLink

<NavBars>
<NavBar Name="$Resources:core,category_Top;"
    Separator="&amp;nbsp;&amp;nbsp;&amp;nbsp;"
    Body="&lt;a ID='onettopnavbar#LABEL_ID#' href='#URL#' accesskey='J'&gt;#LABEL#&lt;/a&gt;"
    ID="1002">
  <NavBarLink Name="Hello" Url="/sider/Hello.aspx"/>
  <NavBarLink Name="Hello2" Url="javascript:SayHello();"/>
</NavBar>
</NavBars>

It is much simpler, as simple as unbelievable :). Are there some problems with NavBarLinks?

Edit: I found one shortcoming with NavBarLink: you can’t use resources from cmscore like $Resources:cmscore,List_Pages_UrlName;. To solve we can create a copy of this string in our custom resource file: $Resources:Takana,List_Pages_UrlName;.

Working with web.Properties

Sometimes one may need more properties to track on a SPWeb beside Title and Description. One of the possibilities is to create a custom list (maybe a hidden one) with keys and values (Title and Value). It works fine. The good thing with it is also the possibility to change the key-value pair directly in the web interface.

Another approach is to use web.Properties which is a Dictionary with key-values pairs. A simpler and neater solution:

Here is a good motivation from the best sharepoint book Sharepoint as a Development Platform (p. 1043):

Each site includes a property bag, which is widely used internally by SharePoint. However, there is no reason why you cannot also use this property bag to store common settings related to a site. You can regard the bag as an application settings collection, equivalent to what you would normally define in a web.config or app.config file. As in the configuration files, the settings are a collection of key/value pairs.

To be able to distinguish your values from standard Sharepoint use a prefix. Here are two methods: one for writing a property, and one for listing all properties:

const string PREFIX = "contoso_";

private void  AddEntry(string key, string value) 
{            
	using (SPSite site = new SPSite("http://contoso/"))
	{
		using (SPWeb web = site.RootWeb)
		{
			key = PREFIX + key;
			if (!web.Properties.ContainsKey(key))
			{
				web.Properties.Add(key, value);
				web.AllowUnsafeUpdates = true;
				web.Properties.Update();
			}
		}
	}
}
private void ListEntries()
{
	using (SPSite site = new SPSite("http://contoso/"))
	{
		using (SPWeb web = site.RootWeb)
		{
			foreach (DictionaryEntry entry in web.Properties)
			{
				Console.WriteLine("{0} = {1}", entry.Key, entry.Value);
			}
		}
	}
}

And of course, all this you can do in Powershell (even a response to shorcoming to not be able to change in UI):

 

 

To remove just run handle as every Dictionary:

web.Properties.Remove(key);
web.Properties.Update()

Or:

$web.Properties.Remove("my-key");
$web.Properties.Update()

The same we can do with web application, just here you don’t need to set AllowUnsafeUpdates to true, neither it won’t be neccessary to run webapp.Properties.Update, just update webapp:

$webapp = get-spwebapplication "http://contoso"
$webapp.Properties.Add("hello", "world")
$webapp.Update()

Do an unsafe update in a unified manner

Recently I talked about a WithWeb-pattern as described in Jonas Nilssons blog where you can isolate the disposal logic in one place. Another thing is to isolate unsafe updates:

public static class SPWebExtension
{
    public static void UnsafeUpdate(this SPWeb web, Action<SPWeb> action)
    {           
        try
        {
            Log.Info("Trying to do an unsafe update on a web: " + web.Title);
            web.AllowUnsafeUpdates = true;
            action(web);
        }
        catch (Exception e)
        {
            Log.Error(e);
        }
        finally
        {
            web.AllowUnsafeUpdates = false;
        }
    }
}

The Log class is my own class which I presented in my previous post.

TryGetList

How do we get a list? Perhaps like that:

var list = web.Lists[listname"];

But we must be aware of exceptions that can appear and we must handle them. A better way to get a list is to use TryGetList:

var list = web.Lists.TryGetList(listname);

WithWeb-pattern of Jonas Nilsson

Jonas Nilsson shows an interesting approach for working with SPSite and SPWeb which must be disposed. Create a helper method WithWeb and send an Action parameter:

public void WithWeb(string uri, Action<SPWeb> action)
{
    using (SPSite site = new SPSite(uri))
    {
        using (SPWeb web = site.OpenWeb())
        {
            action(web);
        }
    }
}

Here is my implementation of this pattern:

public static class DisposalService
{
    public static void WithWeb(string uri, Action<SPWeb> action)
    {
        using (var site = new SPSite(uri))
        {
            using (var web = site.OpenWeb())
            {
                web.UnsafeUpdate(action);
            }
        }
    }
    public  static void WithElevatedWeb(string uri, Action<SPWeb> action)
    {
        SPSecurity.RunWithElevatedPrivileges(() => WithWeb(uri, action));
    }
}

Here I use another fancy way to consolidate the unsafe updates. I added an additional method: WithElevatedWeb, it creates the web as system account.

And here comes an example of using this static method:

Action<SPWeb> update = (SPWeb w) =>
                        {
                            UserService.UpdateOwners(owners, w);
                            UserService.UpdateMembers(members, w);
                        };
DisposalService.WithWeb(SPContext.Current.Web.Url, update);

Uppdatera web med js

Här är ett litet exempel:

 function updateTitle() { 
   var ctx = new SP.ClientContext.get_current();
   this.web = ctx.get_web(); 
   web.set_title('Examensarbete 2011'); 
   this.web.update(); 
  ctx.executeQueryAsync(
     Function.createDelegate(this, this.onUpdate), 
     Function.createDelegate(this, this.onFail)); } 
function onUpdate(sender, args) { 
   alert('title updated'); 
} 
function onFail(sender, args) { 
   alert('failed to update title. Error:'+args.get_message()); 
}

Ge andra rättigheter på default.aspx

Om man inte vill medlemmarna på SPWeb rättighet att redigera första sidan hur som helst, måste man bryta arvet på Sidor/default.aspx. Lätt att göra det manuellt (Site Actions – Show all Content – Pages – default.aspx – dokumenträttigheter).

I koden kan man göra det så här:

//first find SPListItem defaultAspx
//then find SPGroup members (perhaps via web.AssociatedMemberGroup
defaultAspx.BreakRoleInheritance(true);
defaultAspx.RoleAssignments.RemoveFromCurrentScopeOnly(members);
SPRoleAssignment roles = new SPRoleAssignment(members);
SPRoleDefinition perms = web.RoleDefinitions.GetByType(SPRoleType.Reader);
roles.RoleDefinitionBindings.Add(perms);
defaultAspx.RoleAssignments.Add(roles);

~masterurl/default.master & ~masterurl/custom.master

Läser “SharePoint 2010 as a Development Platform”. Kan verkligen rekommendera den. Idag har jag förståt vad default.master och custom.master innebär. De pekar på de master-filer som är inställda på web-nivå. Så det är ingen idé att ändra DynamicMasterUrl i @Page-direktivet till sin egen (om du inte vill ha någon helt annan master än i resten av portalen).

Discovering SharePoint

And having fun doing it

Bram de Jager talking Office 365, SharePoint and Azure

My view and thoughts on Productivity and more.

My programming life

and everything in between

SharePoint Development Lab by @avishnyakov

It is a good place to share some SharePoint stories and development practices.

SharePoint Dragons

Nikander & Margriet on SharePoint

RealActivity - Real-time and trustworthy

Blog site of founder, RealActivty - Paul J. Swider

Mai Omar Desouki - Avid SharePointer

Egyptian & Vodafoner - Senior SharePoint Consultant

Cameron Dwyer | Office 365, SharePoint, Outlook, OnePlace Solutions

Office 365, SharePoint, OnePlace Solutions & Life's Other Little Wonders

paul.tavares

Me and My doings!

Share SharePoint Points!!

By Mohit Vashishtha

Jimmy Janlén "Den Scrummande Konsulten"

Erfarenheter, synpunkter och raljerande om Scrum från Jimmy Janlén

SPJoel

SharePoint for everyone

SharePointRyan

Ryan Dennis is a SharePoint Solution Architect with a passion for SharePoint and PowerShell

SharePoint 2020

The Vision for a Future of Clarity

Aharoni in Unicode, ya mama

Treacle tarts for great justice

... And All That JS

JavaScript, Web Apps and SharePoint

blksthl

Mostly what I know about SharePoint - CommunicoCuspis