Skip to content
This repository was archived by the owner on Jan 19, 2021. It is now read-only.

Commit 5ab7d38

Browse files
committed
Feature : Add-PnPFile(s)ToProvisioningTemplate extract web part
1 parent 41b9ee1 commit 5ab7d38

File tree

3 files changed

+181
-23
lines changed

3 files changed

+181
-23
lines changed

Commands/Base/BaseFileProvisioningCmdlet.cs

Lines changed: 131 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
using OfficeDevPnP.Core.Framework.Provisioning.Providers;
66
using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml;
77
using SharePointPnP.PowerShell.Commands.Provisioning;
8+
using SharePointPnP.PowerShell.Commands.Utilities;
89
using System;
10+
using System.Collections.Generic;
911
using System.IO;
1012
using System.Linq;
1113
using System.Management.Automation;
12-
using System.Net;
14+
using System.Text.RegularExpressions;
1315
using PnPFileLevel = OfficeDevPnP.Core.Framework.Provisioning.Model.FileLevel;
1416
using SPFile = Microsoft.SharePoint.Client.File;
1517

@@ -35,9 +37,29 @@ public class BaseFileProvisioningCmdlet : PnPWebCmdlet
3537
[Parameter(Mandatory = false, Position = 5, HelpMessage = "Set to overwrite in site, Defaults to true")]
3638
public SwitchParameter FileOverwrite = true;
3739

38-
[Parameter(Mandatory = false, Position = 6, HelpMessage = "Allows you to specify ITemplateProviderExtension to execute while loading the template.")]
40+
[Parameter(Mandatory = false, Position = 6, ParameterSetName = PSNAME_REMOTE_SOURCE, HelpMessage = "Include webparts when the file is a page")]
41+
public SwitchParameter ExtractWebParts = true;
42+
43+
[Parameter(Mandatory = false, Position = 7, HelpMessage = "Allows you to specify ITemplateProviderExtension to execute while loading the template.")]
3944
public ITemplateProviderExtension[] TemplateProviderExtensions;
4045

46+
protected readonly ProgressRecord _progressEnumeration = new ProgressRecord(0, "Activity", "Status") { Activity = "Enumerating folder" };
47+
protected readonly ProgressRecord _progressFilesEnumeration = new ProgressRecord(1, "Activity", "Status") { Activity = "Extracting files" };
48+
protected readonly ProgressRecord _progressFileProcessing = new ProgressRecord(2, "Activity", "Status") { Activity = "Extracting file" };
49+
50+
protected override void ProcessRecord()
51+
{
52+
base.ProcessRecord();
53+
var ctx = (ClientContext)SelectedWeb.Context;
54+
ctx.Load(SelectedWeb, web => web.Id, web => web.ServerRelativeUrl, web => web.Url);
55+
if (ExtractWebParts)
56+
{
57+
ctx.Load(ctx.Site, site => site.Id, site => site.ServerRelativeUrl, site => site.Url);
58+
ctx.Load(SelectedWeb.Lists, lists => lists.Include(l => l.Title, l => l.RootFolder.ServerRelativeUrl, l => l.Id));
59+
}
60+
ctx.ExecuteQueryRetry();
61+
}
62+
4163
protected ProvisioningTemplate LoadTemplate()
4264
{
4365
if (!System.IO.Path.IsPathRooted(Path))
@@ -65,8 +87,20 @@ protected ProvisioningTemplate LoadTemplate()
6587
/// <param name="folder">target folder in the provisioning template</param>
6688
/// <param name="fileName">Name of the file</param>
6789
/// <param name="container">Container path within the template (pnp file) or related to the xml templage</param>
68-
protected void AddFileToTemplate(ProvisioningTemplate template, Stream fs, string folder, string fileName, string container)
90+
/// <param name="webParts">WebParts to include</param>
91+
protected void AddFileToTemplate(
92+
ProvisioningTemplate template,
93+
Stream fs,
94+
string folder,
95+
string fileName,
96+
string container,
97+
IEnumerable<WebPart> webParts = null
98+
)
6999
{
100+
if (template == null) throw new ArgumentNullException(nameof(template));
101+
if (fs == null) throw new ArgumentNullException(nameof(fs));
102+
if (fileName == null) throw new ArgumentNullException(nameof(fileName));
103+
70104
var source = !string.IsNullOrEmpty(container) ? (container + "/" + fileName) : fileName;
71105

72106
template.Connector.SaveFileStream(fileName, container, fs);
@@ -86,9 +120,11 @@ protected void AddFileToTemplate(ProvisioningTemplate template, Stream fs, strin
86120
Src = source,
87121
Folder = folder,
88122
Level = FileLevel,
89-
Overwrite = FileOverwrite,
123+
Overwrite = FileOverwrite
90124
};
91125

126+
if (webParts != null) newFile.WebParts.AddRange(webParts);
127+
92128
template.Files.Add(newFile);
93129

94130
// Determine the output file name and path
@@ -119,25 +155,72 @@ protected void AddFileToTemplate(ProvisioningTemplate template, Stream fs, strin
119155
/// <param name="file">The SharePoint file to retrieve and add</param>
120156
protected void AddSPFileToTemplate(ProvisioningTemplate template, SPFile file)
121157
{
158+
if (template == null) throw new ArgumentNullException(nameof(template));
159+
if (file == null) throw new ArgumentNullException(nameof(file));
160+
122161
file.EnsureProperties(f => f.Name, f => f.ServerRelativeUrl);
162+
163+
_progressFileProcessing.StatusDescription = $"Extracting file {file.ServerRelativeUrl}";
123164
var folderRelativeUrl = file.ServerRelativeUrl.Substring(0, file.ServerRelativeUrl.Length - file.Name.Length - 1);
124165
var folderWebRelativeUrl = HttpUtility.UrlKeyValueDecode(folderRelativeUrl.Substring(SelectedWeb.ServerRelativeUrl.TrimEnd('/').Length + 1));
125166
if (ClientContext.HasPendingRequest) ClientContext.ExecuteQuery();
126167
try
127168
{
169+
IEnumerable<WebPart> webParts = null;
170+
if (ExtractWebParts)
171+
{
172+
webParts = ExtractSPFileWebParts(file).ToArray();
173+
_progressFileProcessing.PercentComplete = 25;
174+
_progressFileProcessing.StatusDescription = $"Extracting webpart from {file.ServerRelativeUrl} ";
175+
WriteProgress(_progressFileProcessing);
176+
}
177+
128178
using (var fi = SPFile.OpenBinaryDirect(ClientContext, file.ServerRelativeUrl))
129179
using (var ms = new MemoryStream())
130180
{
181+
_progressFileProcessing.PercentComplete = 50;
182+
_progressFileProcessing.StatusDescription = $"Reading file {file.ServerRelativeUrl}";
183+
WriteProgress(_progressFileProcessing);
131184
// We are using a temporary memory stream because the file connector is seeking in the stream
132185
// and the stream provided by OpenBinaryDirect does not allow it
133186
fi.Stream.CopyTo(ms);
134187
ms.Position = 0;
135-
AddFileToTemplate(template, ms, folderWebRelativeUrl, file.Name, folderWebRelativeUrl);
188+
AddFileToTemplate(template, ms, folderWebRelativeUrl, file.Name, folderWebRelativeUrl, webParts);
189+
_progressFileProcessing.PercentComplete = 100;
190+
_progressFileProcessing.StatusDescription = $"Adding file {file.ServerRelativeUrl} to template";
191+
_progressFileProcessing.RecordType = ProgressRecordType.Completed;
192+
WriteProgress(_progressFileProcessing);
136193
}
137194
}
138-
catch (WebException exc)
195+
catch (Exception exc)
196+
{
197+
WriteWarning($"Error trying to add file {file.ServerRelativeUrl} : {exc.Message}");
198+
}
199+
}
200+
201+
private IEnumerable<WebPart> ExtractSPFileWebParts(SPFile file)
202+
{
203+
if (file == null) throw new ArgumentNullException(nameof(file));
204+
205+
if (string.Compare(System.IO.Path.GetExtension(file.Name), ".aspx", true) == 0)
139206
{
140-
WriteWarning($"Can't add file from url {file.ServerRelativeUrl} : {exc}");
207+
foreach (var spwp in SelectedWeb.GetWebParts(file.ServerRelativeUrl))
208+
{
209+
spwp.EnsureProperties(wp => wp.WebPart
210+
#if !SP2016 // Missing ZoneId property in SP2016 version of the CSOM Library
211+
, wp => wp.ZoneId
212+
#endif
213+
);
214+
yield return new WebPart
215+
{
216+
Contents = Tokenize(SelectedWeb.GetWebPartXml(spwp.Id, file.ServerRelativeUrl)),
217+
Order = (uint)spwp.WebPart.ZoneIndex,
218+
Title = spwp.WebPart.Title,
219+
#if !SP2016 // Missing ZoneId property in SP2016 version of the CSOM Library
220+
Zone = spwp.ZoneId
221+
#endif
222+
};
223+
}
141224
}
142225
}
143226

@@ -149,12 +232,49 @@ protected void AddSPFileToTemplate(ProvisioningTemplate template, SPFile file)
149232
/// <param name="folder">Destination folder of the added file</param>
150233
protected void AddLocalFileToTemplate(ProvisioningTemplate template, string file, string folder)
151234
{
152-
var fileName = System.IO.Path.GetFileName(file);
153-
var container = !string.IsNullOrEmpty(Container) ? Container : folder.Replace("\\", "/");
154-
using (var fs = System.IO.File.OpenRead(file))
235+
if (template == null) throw new ArgumentNullException(nameof(template));
236+
if (file == null) throw new ArgumentNullException(nameof(file));
237+
if (folder == null) throw new ArgumentNullException(nameof(folder));
238+
239+
_progressFileProcessing.Activity = $"Extracting file {file}";
240+
_progressFileProcessing.StatusDescription = "Adding file {file}";
241+
_progressFileProcessing.PercentComplete = 0;
242+
WriteProgress(_progressFileProcessing);
243+
244+
try
245+
{
246+
var fileName = System.IO.Path.GetFileName(file);
247+
var container = !string.IsNullOrEmpty(Container) ? Container : folder.Replace("\\", "/");
248+
249+
using (var fs = System.IO.File.OpenRead(file))
250+
{
251+
AddFileToTemplate(template, fs, folder.Replace("\\", "/"), fileName, container);
252+
}
253+
}
254+
catch (Exception exc)
255+
{
256+
WriteWarning($"Error trying to add file {file} : {exc.Message}");
257+
}
258+
_progressFileProcessing.RecordType = ProgressRecordType.Completed;
259+
WriteProgress(_progressFileProcessing);
260+
}
261+
262+
private string Tokenize(string input)
263+
{
264+
if (string.IsNullOrEmpty(input)) return input;
265+
266+
foreach (var list in SelectedWeb.Lists)
155267
{
156-
AddFileToTemplate(template, fs, folder.Replace("\\", "/"), fileName, container);
268+
input = input
269+
.ReplaceCaseInsensitive(list.Id.ToString("D"), "{listid:" + Regex.Escape(list.Title) + "}")
270+
.ReplaceCaseInsensitive(list.GetWebRelativeUrl(), "{listurl:" + Regex.Escape(list.Title) + "}");
157271
}
272+
return input.ReplaceCaseInsensitive(SelectedWeb.Url, "{site}")
273+
.ReplaceCaseInsensitive(SelectedWeb.ServerRelativeUrl, "{site}")
274+
.ReplaceCaseInsensitive(SelectedWeb.Id.ToString(), "{siteid}")
275+
.ReplaceCaseInsensitive(((ClientContext)SelectedWeb.Context).Site.ServerRelativeUrl, "{sitecollection}")
276+
.ReplaceCaseInsensitive(((ClientContext)SelectedWeb.Context).Site.Id.ToString(), "{sitecollectionid}")
277+
.ReplaceCaseInsensitive(((ClientContext)SelectedWeb.Context).Site.Url, "{sitecollection}");
158278
}
159279
}
160280
}

Commands/Provisioning/AddFileToProvisioningTemplate.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ namespace SharePointPnP.PowerShell.Commands.Provisioning
2727
[CmdletExample(
2828
Code = @"PS:> Add-PnPFileToProvisioningTemplate -Path template.pnp -SourceUrl $urlOfFile",
2929
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.",
30-
SortOrder = 4)]
30+
SortOrder = 5)]
31+
[CmdletExample(
32+
Code = @"PS:> Add-PnPFileToProvisioningTemplate -Path template.pnp -SourceUrl $urlOfFile -ExtractWebParts:$false",
33+
Remarks = "Adds a file to a PnP Provisioning Template retrieved from the currently connected web, disabling WebPart extraction.",
34+
SortOrder = 6)]
3135
public class AddFileToProvisioningTemplate : BaseFileProvisioningCmdlet
3236
{
3337
/*
@@ -45,10 +49,10 @@ public class AddFileToProvisioningTemplate : BaseFileProvisioningCmdlet
4549

4650
protected override void ProcessRecord()
4751
{
52+
base.ProcessRecord();
4853
var template = LoadTemplate();
4954
if (this.ParameterSetName == PSNAME_REMOTE_SOURCE)
5055
{
51-
SelectedWeb.EnsureProperty(w => w.ServerRelativeUrl);
5256
var sourceUri = new Uri(SourceUrl, UriKind.RelativeOrAbsolute);
5357

5458
// Get the server relative url of the file, whatever the input url is (absolute, server relative or web relative form)
@@ -57,6 +61,10 @@ protected override void ProcessRecord()
5761
SourceUrl.StartsWith("/", StringComparison.Ordinal) ? SourceUrl : // The url is server relative. Take it as is (/sites/web/folder/file)
5862
SelectedWeb.ServerRelativeUrl.TrimEnd('/') + "/" + SourceUrl; // The url is web relative, prepend by the web url (folder/file)
5963

64+
_progressFileProcessing.PercentComplete = 0;
65+
_progressFileProcessing.RecordType = ProgressRecordType.Processing;
66+
_progressFileProcessing.StatusDescription = $"Getting file info {serverRelativeUrl}";
67+
6068
var file = SelectedWeb.GetFileByServerRelativeUrl(serverRelativeUrl);
6169

6270
AddSPFileToTemplate(template, file);
@@ -71,6 +79,11 @@ protected override void ProcessRecord()
7179
// Load the file and add it to the .PNP file
7280
Folder = Folder.Replace("\\", "/");
7381

82+
_progressFileProcessing.PercentComplete = 0;
83+
_progressFileProcessing.RecordType = ProgressRecordType.Processing;
84+
_progressFileProcessing.StatusDescription = $"Getting file info {Source}";
85+
WriteProgress(_progressFileProcessing);
86+
7487
AddLocalFileToTemplate(template, Source, Folder);
7588
}
7689
}

0 commit comments

Comments
 (0)