Da man mit Export-SPWeb bzw. Import-SPWeb bzw. im GUI über granular Backup/Restore nur ganze Document Libraries kopieren kann hab ich mich auf die Suche gemacht und bin hierauf gestoßen. Da das dort aufgeführte “Programm” eher nicht so 1:1 zu verwenden war (und auch bei den Foldern beispielsweise Author/Timestamp nicht setzt) und ich die Geschichte mit Excel, Versionen und verschiedenen Modi etc. nicht benötigte habe ich es entsprechend umgeschrieben (mit Hilfe). Ohne wirklich Errorhandling und kaum Logging – aber es hat einwandfrei funktioniert (SharePoint ist offenbar relativ relaxed wenn man Dinge added die es schon gibt):
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data; using Microsoft.SharePoint; namespace Gallauner { class SharepointListCopy { static void Main(string[] args) { string sSourceSite = "https://oldsite.domain.com"; string sSourceWebName = "SomeWeb"; string sSourceListName = "SomeList"; string sSourceFolderName = "SomePath/MorePath/LastPath"; string sDestSiteName = "https://newsite.domain.com"; string sDestWebName = "SomeNewWeb/SubWeb"; string sDestListName = "SomeNewList"; string sDestFolderName = ""; SPSite oSourceSite; SPList oSourceList; SPWeb oSourceWeb; SPListItemCollection oFolders; Microsoft.SharePoint.Administration.SPWebApplication oWebApp; // open source site/web/list oSourceSite = new SPSite(sSourceSite); oWebApp = oSourceSite.WebApplication; oWebApp.FormDigestSettings.Enabled = false; if (sSourceWebName == "") oSourceWeb = oSourceSite.RootWeb; else oSourceWeb = oSourceSite.OpenWeb(sSourceWebName); oSourceList = oSourceWeb.Lists[sSourceListName]; if (oSourceList != null) { // search source folder oFolders = oSourceList.Folders; foreach (SPListItem oFolder in oFolders) { if (oFolder.Folder.Url == sSourceFolderName) { // copy folder CopyFilesAndSubFolders(oLog, oFolder.Folder, oSourceWeb, sDestSiteName, sDestWebName, sDestListName, sDestFolderName); } } } oWebApp.FormDigestSettings.Enabled = true; } private static void CopyFilesAndSubFolders(Logfile oLog, SPFolder oFolder, SPWeb oSourceWeb,string sDestSiteName, string sDestWebName,string sDestListName, string sDestFolderName) { SPSite oDestSite; SPWeb oDestWeb; SPList oDestList; SPFolder oDestFolder, oNewDestFolder; SPFile oFileCopy; SPUser oUser; // open site/web/list oDestSite = new SPSite(sDestSiteName); if (sDestWebName == "") oDestWeb = oDestSite.RootWeb; else oDestWeb = oDestSite.OpenWeb(sDestWebName); oDestList = oDestWeb.Lists[sDestListName]; // find folder oDestFolder = null; if (sDestFolderName == "") oDestFolder = oDestList.RootFolder; else { foreach (SPListItem oTmpFolder in oDestList.Folders) { if (oTmpFolder.Folder.Url == sDestFolderName) { oDestFolder = oTmpFolder.Folder; break; } } } if (oDestFolder == null) { Console.WriteLine($"Cannot find destination folder '{sDestFolderName}'"); return; } // copy files foreach (SPFile oFile in oFolder.Files) { Console.WriteLine($"FILE {oFile.Name}"); try { // add modifying user to destination web oUser=oDestWeb.EnsureUser( (string)oFile.Properties["vti_modifiedby"] ); } catch (Exception) { // add failed, use me as modifying user oUser = oDestWeb.CurrentUser; } oFileCopy = oDestFolder.Files.Add( $"{oDestWeb.Url}/{oDestFolder.Url}/{oFile.Name}", oFile.OpenBinaryStream(), oFile.Properties, oUser, oUser, (DateTime)oFile.Properties["vti_timelastmodified"], (DateTime)oFile.Properties["vti_timelastmodified"], oFile.CheckInComment, true); } // copy folders foreach (SPFolder oSubFolder in oFolder.SubFolders) { Console.WriteLine($"FOLDER {oSubFolder.Url}"); try { // add modifying user to destination web oUser = oDestWeb.EnsureUser( (string)oSubFolder.Properties["vti_modifiedby"] ); } catch (Exception) { // add failed, use me as modifying user oUser = oDestWeb.CurrentUser; } // first create on destination list oNewDestFolder = oDestFolder.SubFolders.Add(oSubFolder.Name); // set author/editor and timestamps SPListItem oTmp = oDestList.GetItemByUniqueId(oNewDestFolder.UniqueId); oTmp["Author"] = oUser; oTmp["Editor"] = oUser; oTmp["Created"] = oSubFolder.Properties["vti_nexttolasttimemodified"]; oTmp["Modified"] = oSubFolder.Properties["vti_nexttolasttimemodified"]; oTmp.Update(); // call ourself recursively CopyFilesAndSubFolders(oLog, oSubFolder, oSourceWeb, sDestSiteName, sDestWebName, sDestListName, oNewDestFolder.Url); } // cleanup objects oDestWeb.Dispose(); oDestSite.Dispose(); } } }
Die Reference Assembly bekommt man über das Nuget Package “Microsoft.SharePoint”, laufen lassen kann man das Ding dann allerdings nur auf einem SharePoint Server – mit Remote Debugger lässts sich dann aber bequem testen/debuggen.