From bf1d1346c36c337697e4a63daf36ac537a8ff69d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steve=20Beaug=C3=A9?= Date: Mon, 27 Aug 2018 17:27:36 +0200 Subject: [PATCH 1/3] Feature: Add-PnPFileToProvisionningTemplate allow add a file from an url --- .../AddFileToProvisioningTemplate.cs | 157 ++++++++++++------ 1 file changed, 107 insertions(+), 50 deletions(-) diff --git a/Commands/Provisioning/AddFileToProvisioningTemplate.cs b/Commands/Provisioning/AddFileToProvisioningTemplate.cs index dcc1fd08e..8e34dcab8 100644 --- a/Commands/Provisioning/AddFileToProvisioningTemplate.cs +++ b/Commands/Provisioning/AddFileToProvisioningTemplate.cs @@ -1,15 +1,17 @@ -using OfficeDevPnP.Core.Framework.Provisioning.Connectors; +using Microsoft.SharePoint.Client; +using Microsoft.SharePoint.Client.Utilities; +using OfficeDevPnP.Core.Framework.Provisioning.Connectors; using OfficeDevPnP.Core.Framework.Provisioning.Model; using OfficeDevPnP.Core.Framework.Provisioning.Providers; using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml; using SharePointPnP.PowerShell.CmdletHelpAttributes; using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Management.Automation; -using System.Text; -using System.Threading.Tasks; +using System.Net; +using PnPFileLevel = OfficeDevPnP.Core.Framework.Provisioning.Model.FileLevel; +using SPFile = Microsoft.SharePoint.Client.File; namespace SharePointPnP.PowerShell.Commands.Provisioning { @@ -32,23 +34,29 @@ namespace SharePointPnP.PowerShell.Commands.Provisioning Code = @"PS:> Add-PnPFileToProvisioningTemplate -Path template.pnp -Source $sourceFilePath -Folder $targetFolder -Container $container", Remarks = "Adds a file to a PnP Provisioning Template with a custom container for the file", SortOrder = 4)] - - public class AddFileToProvisioningTemplate : PSCmdlet + [CmdletExample( + Code = @"PS:> Add-PnPFileToProvisioningTemplate -Path template.pnp -SourceUrl $urlOfFile", + Remarks = "Adds a file to a PnP Provisioning Template retrieved from the currently connected web. The url can be either full, server relative or Web relative url.", + SortOrder = 4)] + public class AddFileToProvisioningTemplate : PnPWebCmdlet { [Parameter(Mandatory = true, Position = 0, HelpMessage = "Filename of the .PNP Open XML provisioning template to read from, optionally including full path.")] public string Path; - [Parameter(Mandatory = true, Position = 1, HelpMessage = "The file to add to the in-memory template, optionally including full path.")] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "LocalSourceFile", HelpMessage = "The file to add to the in-memory template, optionally including full path.")] public string Source; - [Parameter(Mandatory = true, Position = 2, HelpMessage = "The target Folder for the file to add to the in-memory template.")] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "RemoteSourceFile", HelpMessage = "The file to add to the in-memory template, specifying its url in the current connected Web.")] + public string SourceUrl; + + [Parameter(Mandatory = true, Position = 2, ParameterSetName = "LocalSourceFile", HelpMessage = "The target Folder for the file to add to the in-memory template.")] public string Folder; [Parameter(Mandatory = false, Position = 3, HelpMessage = "The target Container for the file to add to the in-memory template, optional argument.")] public string Container; [Parameter(Mandatory = false, Position = 4, HelpMessage = "The level of the files to add. Defaults to Published")] - public FileLevel FileLevel = FileLevel.Published; + public PnPFileLevel FileLevel = PnPFileLevel.Published; [Parameter(Mandatory = false, Position = 5, HelpMessage = "Set to overwrite in site, Defaults to true")] public SwitchParameter FileOverwrite = true; @@ -62,10 +70,6 @@ protected override void ProcessRecord() { Path = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, Path); } - if(!System.IO.Path.IsPathRooted(Source)) - { - Source = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, Source); - } // Load the template var template = ReadProvisioningTemplate .LoadProvisioningTemplateFromFile(Path, @@ -75,51 +79,104 @@ protected override void ProcessRecord() { throw new ApplicationException("Invalid template file!"); } - - // Load the file and add it to the .PNP file - using (var fs = new FileStream(Source, FileMode.Open, FileAccess.Read, FileShare.Read)) + if (this.ParameterSetName == "RemoteSourceFile") { - Folder = Folder.Replace("\\", "/"); - - var fileName = Source.IndexOf("\\") > 0 ? Source.Substring(Source.LastIndexOf("\\") + 1) : Source; - var container = !string.IsNullOrEmpty(Container) ? Container : string.Empty; - var source = !string.IsNullOrEmpty(container) ? (container + "/" + fileName) : fileName; - - template.Connector.SaveFileStream(fileName, container, fs); - - if (template.Connector is ICommitableFileConnector) + SelectedWeb.EnsureProperty(w => w.ServerRelativeUrl); + var sourceUri = new Uri(SourceUrl, UriKind.RelativeOrAbsolute); + var serverRelativeUrl = + sourceUri.IsAbsoluteUri ? sourceUri.AbsolutePath : + SourceUrl.StartsWith("/", StringComparison.Ordinal) ? SourceUrl : + SelectedWeb.ServerRelativeUrl.TrimEnd('/') + "/" + SourceUrl; + + var file = SelectedWeb.GetFileByServerRelativeUrl(serverRelativeUrl); + + var fileName = file.EnsureProperty(f => f.Name); + var folderRelativeUrl = serverRelativeUrl.Substring(0, serverRelativeUrl.Length - fileName.Length - 1); + var folderWebRelativeUrl = HttpUtility.UrlKeyValueDecode(folderRelativeUrl.Substring(SelectedWeb.ServerRelativeUrl.TrimEnd('/').Length + 1)); + if (ClientContext.HasPendingRequest) ClientContext.ExecuteQuery(); + try { - ((ICommitableFileConnector)template.Connector).Commit(); + using (var fi = SPFile.OpenBinaryDirect(ClientContext, serverRelativeUrl)) + using (var ms = new MemoryStream()) + { + // We are using a temporary memory stream because the file connector is seeking in the stream + // and the stream provided by OpenBinaryDirect does not allow it + fi.Stream.CopyTo(ms); + ms.Position = 0; + AddFileToTemplate(template, ms, folderWebRelativeUrl, fileName, folderWebRelativeUrl); + } } - - template.Files.Add(new OfficeDevPnP.Core.Framework.Provisioning.Model.File + catch (WebException exc) { - Src = source, - Folder = Folder, - Level = FileLevel, - Overwrite = FileOverwrite, - }); - - // Determine the output file name and path - var outFileName = System.IO.Path.GetFileName(Path); - var outPath = new FileInfo(Path).DirectoryName; - - var fileSystemConnector = new FileSystemConnector(outPath, ""); - var formatter = XMLPnPSchemaFormatter.LatestFormatter; - var extension = new FileInfo(Path).Extension.ToLowerInvariant(); - if (extension == ".pnp") + WriteWarning($"Can't add file from url {serverRelativeUrl} : {exc}"); + } + } + else + { + if (!System.IO.Path.IsPathRooted(Source)) { - var provider = new XMLOpenXMLTemplateProvider(template.Connector as OpenXMLConnector); - var templateFileName = outFileName.Substring(0, outFileName.LastIndexOf(".", StringComparison.Ordinal)) + ".xml"; - - provider.SaveAs(template, templateFileName, formatter, TemplateProviderExtensions); + Source = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, Source); } - else + + // Load the file and add it to the .PNP file + using (var fs = System.IO.File.OpenRead(Source)) { - XMLTemplateProvider provider = new XMLFileSystemTemplateProvider(Path, ""); - provider.SaveAs(template, Path, formatter, TemplateProviderExtensions); + Folder = Folder.Replace("\\", "/"); + + var fileName = Source.IndexOf("\\", StringComparison.Ordinal) > 0 ? Source.Substring(Source.LastIndexOf("\\") + 1) : Source; + var container = !string.IsNullOrEmpty(Container) ? Container : string.Empty; + AddFileToTemplate(template, fs, Folder, fileName, container); } } } + + private void AddFileToTemplate(ProvisioningTemplate template, Stream fs, string folder, string fileName, string container) + { + var source = !string.IsNullOrEmpty(container) ? (container + "/" + fileName) : fileName; + + template.Connector.SaveFileStream(fileName, container, fs); + + if (template.Connector is ICommitableFileConnector) + { + ((ICommitableFileConnector)template.Connector).Commit(); + } + + var existing = template.Files.FirstOrDefault(f => + f.Src == $"{container}/{fileName}" + && f.Folder == folder); + + if (existing != null) + template.Files.Remove(existing); + + var newFile = new OfficeDevPnP.Core.Framework.Provisioning.Model.File + { + Src = source, + Folder = folder, + Level = FileLevel, + Overwrite = FileOverwrite, + }; + + template.Files.Add(newFile); + + // Determine the output file name and path + var outFileName = System.IO.Path.GetFileName(Path); + var outPath = new FileInfo(Path).DirectoryName; + + var fileSystemConnector = new FileSystemConnector(outPath, ""); + var formatter = XMLPnPSchemaFormatter.LatestFormatter; + var extension = new FileInfo(Path).Extension.ToLowerInvariant(); + if (extension == ".pnp") + { + var provider = new XMLOpenXMLTemplateProvider(template.Connector as OpenXMLConnector); + var templateFileName = outFileName.Substring(0, outFileName.LastIndexOf(".", StringComparison.Ordinal)) + ".xml"; + + provider.SaveAs(template, templateFileName, formatter, TemplateProviderExtensions); + } + else + { + XMLTemplateProvider provider = new XMLFileSystemTemplateProvider(Path, ""); + provider.SaveAs(template, Path, formatter, TemplateProviderExtensions); + } + } } -} +} \ No newline at end of file From 41b9ee1885ea37bcf0992c1a254b1fd128dd6f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steve=20Beaug=C3=A9?= Date: Mon, 27 Aug 2018 21:03:54 +0200 Subject: [PATCH 2/3] Feature: new command Add-PnPFilesToProvisioningTemplate --- Commands/Base/BaseFileProvisioningCmdlet.cs | 160 ++++++++++++++++++ .../AddFileToProvisioningTemplate.cs | 138 ++------------- .../AddFilesToProvisioningTemplate.cs | 123 ++++++++++++++ .../SharePointPnP.PowerShell.Commands.csproj | 5 + 4 files changed, 305 insertions(+), 121 deletions(-) create mode 100644 Commands/Base/BaseFileProvisioningCmdlet.cs create mode 100644 Commands/Provisioning/AddFilesToProvisioningTemplate.cs diff --git a/Commands/Base/BaseFileProvisioningCmdlet.cs b/Commands/Base/BaseFileProvisioningCmdlet.cs new file mode 100644 index 000000000..96cb46f9a --- /dev/null +++ b/Commands/Base/BaseFileProvisioningCmdlet.cs @@ -0,0 +1,160 @@ +using Microsoft.SharePoint.Client; +using Microsoft.SharePoint.Client.Utilities; +using OfficeDevPnP.Core.Framework.Provisioning.Connectors; +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using OfficeDevPnP.Core.Framework.Provisioning.Providers; +using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml; +using SharePointPnP.PowerShell.Commands.Provisioning; +using System; +using System.IO; +using System.Linq; +using System.Management.Automation; +using System.Net; +using PnPFileLevel = OfficeDevPnP.Core.Framework.Provisioning.Model.FileLevel; +using SPFile = Microsoft.SharePoint.Client.File; + +namespace SharePointPnP.PowerShell.Commands +{ + /// + /// Base class for commands related to adding file to template + /// + public class BaseFileProvisioningCmdlet : PnPWebCmdlet + { + protected const string PSNAME_LOCAL_SOURCE = "LocalSourceFile"; + protected const string PSNAME_REMOTE_SOURCE = "RemoteSourceFile"; + + [Parameter(Mandatory = true, Position = 0, HelpMessage = "Filename of the .PNP Open XML provisioning template to read from, optionally including full path.")] + public string Path; + + [Parameter(Mandatory = false, Position = 3, HelpMessage = "The target Container for the file to add to the in-memory template, optional argument.")] + public string Container; + + [Parameter(Mandatory = false, Position = 4, HelpMessage = "The level of the files to add. Defaults to Published")] + public PnPFileLevel FileLevel = PnPFileLevel.Published; + + [Parameter(Mandatory = false, Position = 5, HelpMessage = "Set to overwrite in site, Defaults to true")] + public SwitchParameter FileOverwrite = true; + + [Parameter(Mandatory = false, Position = 6, HelpMessage = "Allows you to specify ITemplateProviderExtension to execute while loading the template.")] + public ITemplateProviderExtension[] TemplateProviderExtensions; + + protected ProvisioningTemplate LoadTemplate() + { + if (!System.IO.Path.IsPathRooted(Path)) + { + Path = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, Path); + } + // Load the template + var template = ReadProvisioningTemplate + .LoadProvisioningTemplateFromFile(Path, + TemplateProviderExtensions); + + if (template == null) + { + throw new ApplicationException("Invalid template file!"); + } + + return template; + } + + /// + /// Add a file to the template + /// + /// The provisioning template to add the file to + /// Stream to read the file content + /// target folder in the provisioning template + /// Name of the file + /// Container path within the template (pnp file) or related to the xml templage + protected void AddFileToTemplate(ProvisioningTemplate template, Stream fs, string folder, string fileName, string container) + { + var source = !string.IsNullOrEmpty(container) ? (container + "/" + fileName) : fileName; + + template.Connector.SaveFileStream(fileName, container, fs); + + if (template.Connector is ICommitableFileConnector) + { + ((ICommitableFileConnector)template.Connector).Commit(); + } + + var existing = template.Files.FirstOrDefault(f => f.Src == $"{container}/{fileName}" && f.Folder == folder); + + if (existing != null) + template.Files.Remove(existing); + + var newFile = new OfficeDevPnP.Core.Framework.Provisioning.Model.File + { + Src = source, + Folder = folder, + Level = FileLevel, + Overwrite = FileOverwrite, + }; + + template.Files.Add(newFile); + + // Determine the output file name and path + var outFileName = System.IO.Path.GetFileName(Path); + var outPath = new FileInfo(Path).DirectoryName; + + var fileSystemConnector = new FileSystemConnector(outPath, ""); + var formatter = XMLPnPSchemaFormatter.LatestFormatter; + var extension = new FileInfo(Path).Extension.ToLowerInvariant(); + if (extension == ".pnp") + { + var provider = new XMLOpenXMLTemplateProvider(template.Connector as OpenXMLConnector); + var templateFileName = outFileName.Substring(0, outFileName.LastIndexOf(".", StringComparison.Ordinal)) + ".xml"; + + provider.SaveAs(template, templateFileName, formatter, TemplateProviderExtensions); + } + else + { + XMLTemplateProvider provider = new XMLFileSystemTemplateProvider(Path, ""); + provider.SaveAs(template, Path, formatter, TemplateProviderExtensions); + } + } + + /// + /// Adds a remote file to a template + /// + /// Template to add the file to + /// The SharePoint file to retrieve and add + protected void AddSPFileToTemplate(ProvisioningTemplate template, SPFile file) + { + file.EnsureProperties(f => f.Name, f => f.ServerRelativeUrl); + var folderRelativeUrl = file.ServerRelativeUrl.Substring(0, file.ServerRelativeUrl.Length - file.Name.Length - 1); + var folderWebRelativeUrl = HttpUtility.UrlKeyValueDecode(folderRelativeUrl.Substring(SelectedWeb.ServerRelativeUrl.TrimEnd('/').Length + 1)); + if (ClientContext.HasPendingRequest) ClientContext.ExecuteQuery(); + try + { + using (var fi = SPFile.OpenBinaryDirect(ClientContext, file.ServerRelativeUrl)) + using (var ms = new MemoryStream()) + { + // We are using a temporary memory stream because the file connector is seeking in the stream + // and the stream provided by OpenBinaryDirect does not allow it + fi.Stream.CopyTo(ms); + ms.Position = 0; + AddFileToTemplate(template, ms, folderWebRelativeUrl, file.Name, folderWebRelativeUrl); + } + } + catch (WebException exc) + { + WriteWarning($"Can't add file from url {file.ServerRelativeUrl} : {exc}"); + } + } + + /// + /// Adds a local file to a template + /// + /// Template to add the file to + /// Full path to a local file + /// Destination folder of the added file + protected void AddLocalFileToTemplate(ProvisioningTemplate template, string file, string folder) + { + var fileName = System.IO.Path.GetFileName(file); + var container = !string.IsNullOrEmpty(Container) ? Container : folder.Replace("\\", "/"); + using (var fs = System.IO.File.OpenRead(file)) + { + AddFileToTemplate(template, fs, folder.Replace("\\", "/"), fileName, container); + } + } + } +} \ No newline at end of file diff --git a/Commands/Provisioning/AddFileToProvisioningTemplate.cs b/Commands/Provisioning/AddFileToProvisioningTemplate.cs index 8e34dcab8..399d18e98 100644 --- a/Commands/Provisioning/AddFileToProvisioningTemplate.cs +++ b/Commands/Provisioning/AddFileToProvisioningTemplate.cs @@ -1,17 +1,7 @@ using Microsoft.SharePoint.Client; -using Microsoft.SharePoint.Client.Utilities; -using OfficeDevPnP.Core.Framework.Provisioning.Connectors; -using OfficeDevPnP.Core.Framework.Provisioning.Model; -using OfficeDevPnP.Core.Framework.Provisioning.Providers; -using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml; using SharePointPnP.PowerShell.CmdletHelpAttributes; using System; -using System.IO; -using System.Linq; using System.Management.Automation; -using System.Net; -using PnPFileLevel = OfficeDevPnP.Core.Framework.Provisioning.Model.FileLevel; -using SPFile = Microsoft.SharePoint.Client.File; namespace SharePointPnP.PowerShell.Commands.Provisioning { @@ -38,78 +28,38 @@ namespace SharePointPnP.PowerShell.Commands.Provisioning Code = @"PS:> Add-PnPFileToProvisioningTemplate -Path template.pnp -SourceUrl $urlOfFile", Remarks = "Adds a file to a PnP Provisioning Template retrieved from the currently connected web. The url can be either full, server relative or Web relative url.", SortOrder = 4)] - public class AddFileToProvisioningTemplate : PnPWebCmdlet + public class AddFileToProvisioningTemplate : BaseFileProvisioningCmdlet { - [Parameter(Mandatory = true, Position = 0, HelpMessage = "Filename of the .PNP Open XML provisioning template to read from, optionally including full path.")] - public string Path; + /* +* Path, FileLevel, FileOverwrite and TemplateProviderExtensions fields are in the base class +* */ - [Parameter(Mandatory = true, Position = 1, ParameterSetName = "LocalSourceFile", HelpMessage = "The file to add to the in-memory template, optionally including full path.")] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = PSNAME_LOCAL_SOURCE, HelpMessage = "The file to add to the in-memory template, optionally including full path.")] public string Source; - [Parameter(Mandatory = true, Position = 1, ParameterSetName = "RemoteSourceFile", HelpMessage = "The file to add to the in-memory template, specifying its url in the current connected Web.")] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = PSNAME_REMOTE_SOURCE, HelpMessage = "The file to add to the in-memory template, specifying its url in the current connected Web.")] public string SourceUrl; - [Parameter(Mandatory = true, Position = 2, ParameterSetName = "LocalSourceFile", HelpMessage = "The target Folder for the file to add to the in-memory template.")] + [Parameter(Mandatory = true, Position = 2, ParameterSetName = PSNAME_LOCAL_SOURCE, HelpMessage = "The target Folder for the file to add to the in-memory template.")] public string Folder; - [Parameter(Mandatory = false, Position = 3, HelpMessage = "The target Container for the file to add to the in-memory template, optional argument.")] - public string Container; - - [Parameter(Mandatory = false, Position = 4, HelpMessage = "The level of the files to add. Defaults to Published")] - public PnPFileLevel FileLevel = PnPFileLevel.Published; - - [Parameter(Mandatory = false, Position = 5, HelpMessage = "Set to overwrite in site, Defaults to true")] - public SwitchParameter FileOverwrite = true; - - [Parameter(Mandatory = false, Position = 4, HelpMessage = "Allows you to specify ITemplateProviderExtension to execute while loading the template.")] - public ITemplateProviderExtension[] TemplateProviderExtensions; - protected override void ProcessRecord() { - if (!System.IO.Path.IsPathRooted(Path)) - { - Path = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, Path); - } - // Load the template - var template = ReadProvisioningTemplate - .LoadProvisioningTemplateFromFile(Path, - TemplateProviderExtensions); - - if (template == null) - { - throw new ApplicationException("Invalid template file!"); - } - if (this.ParameterSetName == "RemoteSourceFile") + var template = LoadTemplate(); + if (this.ParameterSetName == PSNAME_REMOTE_SOURCE) { SelectedWeb.EnsureProperty(w => w.ServerRelativeUrl); var sourceUri = new Uri(SourceUrl, UriKind.RelativeOrAbsolute); + + // Get the server relative url of the file, whatever the input url is (absolute, server relative or web relative form) var serverRelativeUrl = - sourceUri.IsAbsoluteUri ? sourceUri.AbsolutePath : - SourceUrl.StartsWith("/", StringComparison.Ordinal) ? SourceUrl : - SelectedWeb.ServerRelativeUrl.TrimEnd('/') + "/" + SourceUrl; + sourceUri.IsAbsoluteUri ? sourceUri.AbsolutePath : // The url is absolute, extract the absolute path (http://server/sites/web/folder/file) + SourceUrl.StartsWith("/", StringComparison.Ordinal) ? SourceUrl : // The url is server relative. Take it as is (/sites/web/folder/file) + SelectedWeb.ServerRelativeUrl.TrimEnd('/') + "/" + SourceUrl; // The url is web relative, prepend by the web url (folder/file) var file = SelectedWeb.GetFileByServerRelativeUrl(serverRelativeUrl); - var fileName = file.EnsureProperty(f => f.Name); - var folderRelativeUrl = serverRelativeUrl.Substring(0, serverRelativeUrl.Length - fileName.Length - 1); - var folderWebRelativeUrl = HttpUtility.UrlKeyValueDecode(folderRelativeUrl.Substring(SelectedWeb.ServerRelativeUrl.TrimEnd('/').Length + 1)); - if (ClientContext.HasPendingRequest) ClientContext.ExecuteQuery(); - try - { - using (var fi = SPFile.OpenBinaryDirect(ClientContext, serverRelativeUrl)) - using (var ms = new MemoryStream()) - { - // We are using a temporary memory stream because the file connector is seeking in the stream - // and the stream provided by OpenBinaryDirect does not allow it - fi.Stream.CopyTo(ms); - ms.Position = 0; - AddFileToTemplate(template, ms, folderWebRelativeUrl, fileName, folderWebRelativeUrl); - } - } - catch (WebException exc) - { - WriteWarning($"Can't add file from url {serverRelativeUrl} : {exc}"); - } + AddSPFileToTemplate(template, file); } else { @@ -119,63 +69,9 @@ protected override void ProcessRecord() } // Load the file and add it to the .PNP file - using (var fs = System.IO.File.OpenRead(Source)) - { - Folder = Folder.Replace("\\", "/"); - - var fileName = Source.IndexOf("\\", StringComparison.Ordinal) > 0 ? Source.Substring(Source.LastIndexOf("\\") + 1) : Source; - var container = !string.IsNullOrEmpty(Container) ? Container : string.Empty; - AddFileToTemplate(template, fs, Folder, fileName, container); - } - } - } - - private void AddFileToTemplate(ProvisioningTemplate template, Stream fs, string folder, string fileName, string container) - { - var source = !string.IsNullOrEmpty(container) ? (container + "/" + fileName) : fileName; - - template.Connector.SaveFileStream(fileName, container, fs); - - if (template.Connector is ICommitableFileConnector) - { - ((ICommitableFileConnector)template.Connector).Commit(); - } - - var existing = template.Files.FirstOrDefault(f => - f.Src == $"{container}/{fileName}" - && f.Folder == folder); - - if (existing != null) - template.Files.Remove(existing); + Folder = Folder.Replace("\\", "/"); - var newFile = new OfficeDevPnP.Core.Framework.Provisioning.Model.File - { - Src = source, - Folder = folder, - Level = FileLevel, - Overwrite = FileOverwrite, - }; - - template.Files.Add(newFile); - - // Determine the output file name and path - var outFileName = System.IO.Path.GetFileName(Path); - var outPath = new FileInfo(Path).DirectoryName; - - var fileSystemConnector = new FileSystemConnector(outPath, ""); - var formatter = XMLPnPSchemaFormatter.LatestFormatter; - var extension = new FileInfo(Path).Extension.ToLowerInvariant(); - if (extension == ".pnp") - { - var provider = new XMLOpenXMLTemplateProvider(template.Connector as OpenXMLConnector); - var templateFileName = outFileName.Substring(0, outFileName.LastIndexOf(".", StringComparison.Ordinal)) + ".xml"; - - provider.SaveAs(template, templateFileName, formatter, TemplateProviderExtensions); - } - else - { - XMLTemplateProvider provider = new XMLFileSystemTemplateProvider(Path, ""); - provider.SaveAs(template, Path, formatter, TemplateProviderExtensions); + AddLocalFileToTemplate(template, Source, Folder); } } } diff --git a/Commands/Provisioning/AddFilesToProvisioningTemplate.cs b/Commands/Provisioning/AddFilesToProvisioningTemplate.cs new file mode 100644 index 000000000..5cd0814cd --- /dev/null +++ b/Commands/Provisioning/AddFilesToProvisioningTemplate.cs @@ -0,0 +1,123 @@ +using Microsoft.SharePoint.Client; +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Management.Automation; +using SPFile = Microsoft.SharePoint.Client.File; + +namespace SharePointPnP.PowerShell.Commands.Provisioning +{ + [Cmdlet(VerbsCommon.Add, "PnPFilesToProvisioningTemplate")] + [CmdletHelp("Adds files to a PnP Provisioning Template", + Category = CmdletHelpCategory.Provisioning)] + [CmdletExample( + Code = @"PS:> Add-PnPFilesToProvisioningTemplate -Path template.pnp -SourceFolder $sourceFolder -Folder $targetFolder", + Remarks = "Adds files to a PnP Provisioning Template from a local folder", + SortOrder = 1)] + [CmdletExample( + Code = @"PS:> Add-PnPFileToProvisioningTemplate -Path template.xml -SourceFolder $sourceFolder -Folder $targetFolder", + Remarks = "Adds files reference to a PnP Provisioning XML Template", + SortOrder = 2)] + [CmdletExample( + Code = @"PS:> Add-PnPFileToProvisioningTemplate -Path template.pnp -SourceFolder ""./myfolder"" -Folder ""folderinsite"" -FileLevel Published -FileOverwrite:$false", + Remarks = "Adds files to a PnP Provisioning Template, specifies the level as Published and defines to not overwrite the files if it exists in the site.", + SortOrder = 3)] + [CmdletExample( + Code = @"PS:> Add-PnPFileToProvisioningTemplate -Path template.pnp -SourceFolder ""./myfolder"" -Recurse", + Remarks = "Adds files to a PnP Provisioning Template from a local folder recursively.", + SortOrder = 4)] + [CmdletExample( + Code = @"PS:> Add-PnPFileToProvisioningTemplate -Path template.pnp -SourceFolder $sourceFolder -Folder $targetFolder -Container $container", + Remarks = "Adds files to a PnP Provisioning Template with a custom container for the files", + SortOrder = 5)] + [CmdletExample( + Code = @"PS:> Add-PnPFileToProvisioningTemplate -Path template.pnp -SourceFolderUrl $urlOfFolder", + Remarks = "Adds files to a PnP Provisioning Template retrieved from the currently connected web. The url can be either full, server relative or Web relative url.", + SortOrder = 6)] + public class AddFilesToProvisioningTemplate : BaseFileProvisioningCmdlet + { + [Parameter(Mandatory = true, Position = 1, ParameterSetName = PSNAME_LOCAL_SOURCE, HelpMessage = "The source folder to add to the in-memory template, optionally including full path.")] + public string SourceFolder; + + [Parameter(Mandatory = true, Position = 1, ParameterSetName = PSNAME_REMOTE_SOURCE, HelpMessage = "The source folder to add to the in-memory template, specifying its url in the current connected Web.")] + public string SourceFolderUrl; + + [Parameter(Mandatory = true, Position = 2, ParameterSetName = PSNAME_LOCAL_SOURCE, HelpMessage = "The target Folder for the source folder to add to the in-memory template.")] + public string Folder; + + [Parameter(Mandatory = true, Position = 7, ParameterSetName = PSNAME_LOCAL_SOURCE, HelpMessage = "The target Folder for the source folder to add to the in-memory template.")] + public SwitchParameter Recurse = false; + + protected override void ProcessRecord() + { + var template = LoadTemplate(); + if (this.ParameterSetName == PSNAME_REMOTE_SOURCE) + { + SelectedWeb.EnsureProperty(w => w.ServerRelativeUrl); + var sourceUri = new Uri(SourceFolderUrl, UriKind.RelativeOrAbsolute); + // Get the server relative url of the folder, whatever the input url is (absolute, server relative or web relative form) + var serverRelativeUrl = + sourceUri.IsAbsoluteUri ? sourceUri.AbsolutePath : // The url is absolute, extract the absolute path (http://server/sites/web/folder/file) + SourceFolderUrl.StartsWith("/", StringComparison.Ordinal) ? SourceFolderUrl : // The url is server relative. Take it as is (/sites/web/folder/file) + SelectedWeb.ServerRelativeUrl.TrimEnd('/') + "/" + SourceFolderUrl; // The url is web relative, prepend by the web url (folder/file) + + + var folder = SelectedWeb.GetFolderByServerRelativeUrl(serverRelativeUrl); + + var files = EnumRemoteFiles(folder, Recurse).OrderBy(f => f.ServerRelativeUrl); + foreach (var file in files) + { + AddSPFileToTemplate(template, file); + } + } + else + { + if (!System.IO.Path.IsPathRooted(SourceFolder)) + { + SourceFolder = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, SourceFolder); + } + + var files = System.IO.Directory.GetFiles(SourceFolder, "*", Recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly).OrderBy(f => f); + + foreach (var file in files) + { + var localFileFolder = System.IO.Path.GetDirectoryName(file); + // relative folder of the leaf file within the directory structure, under the source folder + var relativeFolder = Folder + localFileFolder.Substring(SourceFolder.Length); + // Load the file and add it to the .PNP file + AddLocalFileToTemplate(template, file, relativeFolder); + } + } + } + + private IEnumerable EnumRemoteFiles(Microsoft.SharePoint.Client.Folder folder, bool recurse) + { + var ctx = folder.Context; + + ctx.Load(folder.Files, files => files.Include(f => f.ServerRelativeUrl, f => f.Name)); + ctx.ExecuteQueryRetry(); + + foreach (var file in folder.Files) + { + yield return file; + } + + if (recurse) + { + ctx.Load(folder.Folders); + ctx.ExecuteQueryRetry(); + + foreach (var subFolder in folder.Folders) + { + foreach (var file in EnumRemoteFiles(subFolder, recurse)) + { + yield return file; + } + } + } + } + } +} \ No newline at end of file diff --git a/Commands/SharePointPnP.PowerShell.Commands.csproj b/Commands/SharePointPnP.PowerShell.Commands.csproj index 5edb933f0..67c661e9b 100644 --- a/Commands/SharePointPnP.PowerShell.Commands.csproj +++ b/Commands/SharePointPnP.PowerShell.Commands.csproj @@ -458,6 +458,7 @@ + @@ -497,6 +498,7 @@ + @@ -934,6 +936,9 @@ + + + From 1d40846538b7b21f6cbc36f6da9b04b9e2c49cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steve=20Beaug=C3=A9?= Date: Mon, 18 Feb 2019 14:33:19 +0100 Subject: [PATCH 3/3] Fix namespace and file path --- .../{ => Site}/AddFilesToProvisioningTemplate.cs | 2 +- Commands/SharePointPnP.PowerShell.Commands.csproj | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) rename Commands/Provisioning/{ => Site}/AddFilesToProvisioningTemplate.cs (99%) diff --git a/Commands/Provisioning/AddFilesToProvisioningTemplate.cs b/Commands/Provisioning/Site/AddFilesToProvisioningTemplate.cs similarity index 99% rename from Commands/Provisioning/AddFilesToProvisioningTemplate.cs rename to Commands/Provisioning/Site/AddFilesToProvisioningTemplate.cs index 5cd0814cd..c060eb7d2 100644 --- a/Commands/Provisioning/AddFilesToProvisioningTemplate.cs +++ b/Commands/Provisioning/Site/AddFilesToProvisioningTemplate.cs @@ -8,7 +8,7 @@ using System.Management.Automation; using SPFile = Microsoft.SharePoint.Client.File; -namespace SharePointPnP.PowerShell.Commands.Provisioning +namespace SharePointPnP.PowerShell.Commands.Provisioning.Site { [Cmdlet(VerbsCommon.Add, "PnPFilesToProvisioningTemplate")] [CmdletHelp("Adds files to a PnP Provisioning Template", diff --git a/Commands/SharePointPnP.PowerShell.Commands.csproj b/Commands/SharePointPnP.PowerShell.Commands.csproj index 557a111df..272604440 100644 --- a/Commands/SharePointPnP.PowerShell.Commands.csproj +++ b/Commands/SharePointPnP.PowerShell.Commands.csproj @@ -636,7 +636,7 @@ - + @@ -1086,9 +1086,7 @@ - - - +