55using OfficeDevPnP . Core . Framework . Provisioning . Providers ;
66using OfficeDevPnP . Core . Framework . Provisioning . Providers . Xml ;
77using SharePointPnP . PowerShell . Commands . Provisioning ;
8+ using SharePointPnP . PowerShell . Commands . Utilities ;
89using System ;
10+ using System . Collections . Generic ;
911using System . IO ;
1012using System . Linq ;
1113using System . Management . Automation ;
12- using System . Net ;
14+ using System . Text . RegularExpressions ;
1315using PnPFileLevel = OfficeDevPnP . Core . Framework . Provisioning . Model . FileLevel ;
1416using 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}
0 commit comments