Friday, 23 May 2014

Using PowerShell, CSOM, and some Hokey Pokey to copy files between two different SharePoint farms

Recently I needed to migrate a large number of images from a SharePoint 2010 farm, to a new SharePoint 2013 farm. I had local server access to the SharePoint 2013 farm servers, but only browser based access to the SharePoint 2010 farm.

PowerShell remoting wasn't an option, so I couple together a couple methods to come up with a solution. Use CSOM (via PoweShell) to get a list of the files (images) on the remote SharePoint library. Then use the WebClient class to download the file, and finally, the standard server based object model to add the downloaded image to the destination SharePoint farm libary.

What the hell, I hear you say?! But why not just use CSOM all the way?! Because, well, actually, I have no good reason. Probably because I was in a hurry, and this was the fastest way I could achieve my objective in the short amount of time I had! And sometimes, a quick solution to a problem is more valuable than "technically perfect" approach! They're my excuses anyway.

So, here's the PowerShell code I used to do it. It's commented, and if nothing else, it demonstrates that there is usually more than one way to approach a problem!! Oh yeah, it also demonstrates using PowerShell and SharePoint CSOM together!


function Import-FilesFromRemoteLibraryUsingCSOM{            
    [CmdletBinding()]                        
    Param(                           
            [parameter(Mandatory=$true)][string]$SourceWebApplicationUrl,            
            [parameter(Mandatory=$true)][string]$SourceWebUrl,            
            [parameter(Mandatory=$true)][string]$SourceLibrary,            
      [parameter(Mandatory=$true)][string]$DestinationWebUrl,            
            [parameter(Mandatory=$true)][string]$DestinationLibrary            
        )            
    #Load the Microsoft SharePoint Client Dll's            
    Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll";            
    Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll";             
    #Using the CSOM in PowerShell is very similar to using it in C#, with just a few minor differences.            
    #Get the Client Context, using the SourceWeb URL passed to the function. This is the web we will be getting the files from.             
    $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($SourceWebUrl)            
    #Load the web            
    $w = $ctx.Web            
    $ctx.Load($w)            
    #Get and load the list that contains the files we want to migrate            
    $l = $w.Lists.GetByTitle($SourceLibrary)            
    $ctx.Load($l)            
    #Create a new query. This is where we could filter the list items (files) that we're going to migrate. In this example, we're just limiting the results to 1000 items.            
    $query = New-Object Microsoft.SharePoint.Client.CamlQuery            
    $query.ViewXml = "1000"            
    $items = $l.GetItems($query)            
    #Load the items            
    $ctx.Load($items)            
    #Execute the loaded queries.             
    $ctx.ExecuteQuery()            
    if($l -eq $null){Write-Host "List not found." -f Red;return;}            
    Write-Host "Attempting to import all library files." -f darkyellow;            
    #Create an array for holding a list of file URL's            
    $fileUrls = @();            
    #Get the destination web and list. Remember that this code is running on the destination SharePoint server, so we can just use the standard SharePoint cmdlet's            
    $dw = Get-SPWeb $DestinationWebUrl;            
    $dl = $dw.Lists[$DestinationLibrary];            
    #Create a WebClient object. We'll use this to download each file to a byte array, which we can upload into SharePoint.             
    #I chose to use this method, as it was quick, I was short on time, and I was having trouble getting a byte array using CSOM.            
    #It demonstrates an alternate approach to a problem!             
    $wc = New-Object System.Net.WebClient;             
    $credentials = [System.Net.CredentialCache]::DefaultCredentials;            
    $wc.Credentials = $credentials;            
    #For each item in the list of items retrieved from our source server (using CSOM), we create the URL, download the file as a byte array, and then upload it to the destination SharePoint server            
    foreach($i in $items){            
        #Load the item and file            
        $item = $l.GetItemById($i.Id);            
        $file = $item.File;            
        $ctx.Load($item);            
        $ctx.Load($file);            
        #Execute the query to get the items we loaded            
        $ctx.ExecuteQuery();            
        #Create the Url for the file            
        $itemUrl = ([String]::Format("{0}{1}",$SourceWebApplicationUrl,$item["FileRef"]));              
        $fileName = $itemUrl.Substring($itemUrl.LastIndexOf("/")+1);            
        Write-Host "Downloading file $itemUrl";            
        #Download the file as a byte array            
        $file = $wc.DownloadData($itemUrl);            
        #Upload the file to the destination SharePoint library            
        $nf = $dw.Files.Add(([String]::Format("{0}/{1}/{2}",$dw.Url,$dl.RootFolder.Url,$fileName)),$file,$true);            
        #If the destination list requires files to be checked in, then check it in!            
        if($nf.RequiresCheckout)            
        {               
         $nf.CheckIn("Checked in during migration","MajorCheckIn")             
        }              
        Write-host "Added file $fileName" -f Green;            
    }                
}