diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.Common/Classes/Helpers/XmlUtility.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.Common/Classes/Helpers/XmlUtility.cs
new file mode 100644
index 000000000..57e772571
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.Common/Classes/Helpers/XmlUtility.cs
@@ -0,0 +1,77 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Xml;
+using System.Xml.Serialization;
+
+namespace PNP.Deployer.Common
+{
+ // =======================================================
+ ///
+ /// Simon-Pierre Plante (sp.plante@gmail.com)
+ ///
+ // =======================================================
+ public static class XmlUtility
+ {
+ #region Public Methods
+
+ // ===========================================================================================================
+ ///
+ /// Validates the specified XML file based on the specified XSD file
+ ///
+ /// The path of the XML file to validate
+ /// The path of the XSD file to be used for validation
+ // ===========================================================================================================
+ public static void ValidateSchema(string xmlPath, string xsdPath)
+ {
+ XmlDocument doc = new XmlDocument();
+ doc.Load(xmlPath);
+ doc.Schemas.Add(null, xsdPath);
+ doc.Validate(null);
+ }
+
+
+ // ===========================================================================================================
+ ///
+ /// Deserializes the specified XML file into the desired type of object based on the specified XML file
+ ///
+ /// The type of object in which the XML needs to be deserialized
+ /// The path of the XML file that needs to be deserialized
+ /// The deserialized XML in the form of the requested type (T)
+ // ===========================================================================================================
+ public static T DeserializeXmlFile(string xmlPath)
+ {
+ T deserializedObject = default(T);
+
+ XmlSerializer serializer = new XmlSerializer(typeof(T));
+ XmlTextReader reader = new XmlTextReader(xmlPath);
+ deserializedObject = (T)serializer.Deserialize(reader);
+
+ return deserializedObject;
+ }
+
+
+ // ===========================================================================================================
+ ///
+ /// Deserializes the specified XML file into the desired type of object based on the specified string reader
+ ///
+ /// The type of object in which the XML needs to be deserialized
+ /// A string that contains the XML that needs to be deserialized
+ /// The deserialized XML in the form of the requested type (T)
+ // ===========================================================================================================
+ public static T DeserializeXml(string xml)
+ {
+ T deserializedObject = default(T);
+
+ using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
+ {
+ XmlSerializer serializer = new XmlSerializer(typeof(T));
+ deserializedObject = (T)serializer.Deserialize(stream);
+ }
+
+ return deserializedObject;
+ }
+
+ #endregion
+ }
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.Common/PNP.Deployer.Common.csproj b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.Common/PNP.Deployer.Common.csproj
new file mode 100644
index 000000000..8535218e4
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.Common/PNP.Deployer.Common.csproj
@@ -0,0 +1,54 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {0FE76181-46FE-451B-B7A0-83E8B455DA5D}
+ Library
+ Properties
+ PNP.Deployer.Common
+ PNP.Deployer.Common
+ v4.5.2
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.Common/Properties/AssemblyInfo.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.Common/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..d6e02bb57
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.Common/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("PNP.Deployer.Common")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("PNP.Deployer.Common")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("0fe76181-46fe-451b-b7a0-83e8b455da5d")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.CSOM/PNP.Deployer.ExtensibilityProviders.CSOM.csproj b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.CSOM/PNP.Deployer.ExtensibilityProviders.CSOM.csproj
new file mode 100644
index 000000000..ffaefaeaf
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.CSOM/PNP.Deployer.ExtensibilityProviders.CSOM.csproj
@@ -0,0 +1,109 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {CA4E46F3-08F9-41E1-B90E-EDCDAD22EC22}
+ Library
+ Properties
+ PNP.Deployer.ExtensibilityProviders.CSOM
+ PNP.Deployer.ExtensibilityProviders.CSOM
+ v4.5.2
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ True
+
+
+
+
+
+ ..\packages\SharePointPnPCore2013.2.6.1608.0\lib\net45\Microsoft.Online.SharePoint.Client.Tenant.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+ ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll
+ True
+
+
+ ..\packages\NLog.4.3.7\lib\net45\NLog.dll
+ True
+
+
+ ..\packages\SharePointPnPCore2013.2.6.1608.0\lib\net45\OfficeDevPnP.Core.dll
+ True
+
+
+
+
+
+
+
+ ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll
+ True
+
+
+
+
+
+ ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {0fe76181-46fe-451b-b7a0-83e8b455da5d}
+ PNP.Deployer.Common
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.CSOM/Properties/AssemblyInfo.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.CSOM/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..c300d83e2
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.CSOM/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("PNP.Deployer.ExtensibilityProviders.CSOM")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("PNP.Deployer.ExtensibilityProviders.CSOM")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("ca4e46f3-08f9-41e1-b90e-edcdad22ec22")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.CSOM/packages.config b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.CSOM/packages.config
new file mode 100644
index 000000000..99deb5d24
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.CSOM/packages.config
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.SSOM/Classes/ExtensibilityProviders/SiteFieldsProvider.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.SSOM/Classes/ExtensibilityProviders/SiteFieldsProvider.cs
new file mode 100644
index 000000000..81efdd1a0
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.SSOM/Classes/ExtensibilityProviders/SiteFieldsProvider.cs
@@ -0,0 +1,117 @@
+using NLog;
+using System;
+using System.Globalization;
+using System.Collections.Generic;
+using OfficeDevPnP.Core.Diagnostics;
+using OfficeDevPnP.Core.Framework.Provisioning.Model;
+using OfficeDevPnP.Core.Framework.Provisioning.Extensibility;
+using OfficeDevPnP.Core.Framework.Provisioning.ObjectHandlers;
+using OfficeDevPnP.Core.Framework.Provisioning.ObjectHandlers.TokenDefinitions;
+using Microsoft.SharePoint;
+using Microsoft.SharePoint.Client;
+using PNP.Deployer.Common;
+
+
+
+// =======================================================
+///
+/// Simon-Pierre Plante (sp.plante@gmail.com)
+///
+// =======================================================
+namespace PNP.Deployer.ExtensibilityProviders.SSOM
+{
+ public class SiteFieldsProvider : IProvisioningExtensibilityHandler
+ {
+ #region Constants
+
+ private const string ERROR_GENERAL = "An error occured while executing the SiteFieldsProvider : {0}";
+
+ #endregion
+
+
+ #region Private Members
+
+ private static Logger logger = LogManager.GetCurrentClassLogger();
+
+ #endregion
+
+
+ #region Interface Methods
+
+ public void Provision(ClientContext ctx, ProvisioningTemplate template, ProvisioningTemplateApplyingInformation applyingInformation, TokenParser tokenParser, PnPMonitoredScope scope, string configurationData)
+ {
+ logger.Info("Entering the SiteFields provider");
+
+ try
+ {
+ // ----------------------------------------------
+ // Deserializes the configuration data
+ // ----------------------------------------------
+ SiteFieldsConfigurationData configData = XmlUtility.DeserializeXml(configurationData);
+
+ // ----------------------------------------------
+ // Loads the site collection and root web
+ // ----------------------------------------------
+ using (SPSite site = new SPSite(ctx.Url))
+ {
+ using (SPWeb web = site.OpenWeb())
+ {
+ // ----------------------------------------------
+ // Loops through the nodes
+ // ----------------------------------------------
+ foreach (Field configField in configData.Fields)
+ {
+ SPField field = web.Fields.GetFieldByInternalName(configField.Name);
+
+ // ----------------------------------------------
+ // Configures the field's title resource
+ // ----------------------------------------------
+ ConfigureFieldTitleResource(field, configField);
+ }
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ throw new Exception(String.Format(ERROR_GENERAL, e.Message), e.InnerException);
+ }
+ }
+
+
+ public ProvisioningTemplate Extract(ClientContext ctx, ProvisioningTemplate template, ProvisioningTemplateCreationInformation creationInformation, PnPMonitoredScope scope, string configurationData)
+ {
+ return template;
+ }
+
+
+ public IEnumerable GetTokens(ClientContext ctx, ProvisioningTemplate template, string configurationData)
+ {
+ return new List();
+ }
+
+ #endregion
+
+
+ #region Private Methods
+
+ // ===========================================================================================================
+ ///
+ /// Configures the TitleResource of the given SPField based on the specified Field config
+ ///
+ /// The SPField that needs to be configured
+ /// The Field config
+ // ===========================================================================================================
+ private void ConfigureFieldTitleResource(SPField field, Field configField)
+ {
+ logger.Info("Configuring title resources for field '{0}'", field.InternalName);
+
+ foreach(TitleResource titleResource in configField.TitleResources)
+ {
+ field.TitleResource.SetValueForUICulture(new CultureInfo(titleResource.LCID), titleResource.Value);
+ }
+ field.Update();
+ }
+
+ #endregion
+ }
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.SSOM/Classes/Serialization/SiteFieldsConfigurationData.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.SSOM/Classes/Serialization/SiteFieldsConfigurationData.cs
new file mode 100644
index 000000000..49f823034
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.SSOM/Classes/Serialization/SiteFieldsConfigurationData.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Xml.Serialization;
+using System.Collections.Generic;
+
+
+// =======================================================
+///
+/// Simon-Pierre Plante (sp.plante@gmail.com)
+///
+// =======================================================
+namespace PNP.Deployer.ExtensibilityProviders.SSOM
+{
+ #region SiteFieldsConfigurationData
+
+ [Serializable()]
+ [XmlRoot(ElementName = "ProviderConfiguration", Namespace = "http://PNP.Deployer/ProviderConfiguration")]
+ public class SiteFieldsConfigurationData
+ {
+ [XmlArray("Fields")]
+ [XmlArrayItem("Field", typeof(Field))]
+ public List Fields { get; set; }
+ }
+
+ #endregion
+
+
+ #region Field
+
+ [Serializable()]
+ public class Field
+ {
+ [XmlAttribute("Name")]
+ public string Name { get; set; }
+
+ [XmlArray("TitleResources")]
+ [XmlArrayItem("TitleResource", typeof(TitleResource))]
+ public List TitleResources { get; set; }
+ }
+
+ #endregion
+
+
+ #region TitleResource
+
+ [Serializable()]
+ public class TitleResource
+ {
+ [XmlAttribute("LCID")]
+ public int LCID { get; set; }
+
+ [XmlAttribute("Value")]
+ public string Value { get; set; }
+ }
+
+ #endregion
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.SSOM/PNP.Deployer.ExtensibilityProviders.SSOM.csproj b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.SSOM/PNP.Deployer.ExtensibilityProviders.SSOM.csproj
new file mode 100644
index 000000000..70f7e9302
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.SSOM/PNP.Deployer.ExtensibilityProviders.SSOM.csproj
@@ -0,0 +1,110 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {DC6CDDA2-4DB6-48A2-BBDB-9C05F84EB235}
+ Library
+ Properties
+ PNP.Deployer.ExtensibilityProviders.SSOM
+ PNP.Deployer.ExtensibilityProviders.SSOM
+ v4.5.2
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ True
+
+
+
+
+
+ ..\packages\SharePointPnPCore2013.2.6.1608.0\lib\net45\Microsoft.Online.SharePoint.Client.Tenant.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+ ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll
+ True
+
+
+ ..\packages\NLog.4.3.7\lib\net45\NLog.dll
+ True
+
+
+ ..\packages\SharePointPnPCore2013.2.6.1608.0\lib\net45\OfficeDevPnP.Core.dll
+ True
+
+
+
+
+
+
+
+ ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll
+ True
+
+
+
+
+
+ ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {0fe76181-46fe-451b-b7a0-83e8b455da5d}
+ PNP.Deployer.Common
+
+
+
+
+
\ No newline at end of file
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.SSOM/Properties/AssemblyInfo.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.SSOM/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..1384c0abd
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.SSOM/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("PNP.Deployer.ExtensibilityProviders.SSOM")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("PNP.Deployer.ExtensibilityProviders.SSOM")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("dc6cdda2-4db6-48a2-bbdb-9c05f84eb235")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.SSOM/packages.config b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.SSOM/packages.config
new file mode 100644
index 000000000..99deb5d24
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.ExtensibilityProviders.SSOM/packages.config
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.sln b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.sln
new file mode 100644
index 000000000..497f809e3
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer.sln
@@ -0,0 +1,40 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PNP.Deployer", "PNP.Deployer\PNP.Deployer.csproj", "{C3C39474-E23C-4C7A-90C1-02153EC8E98A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PNP.Deployer.ExtensibilityProviders.CSOM", "PNP.Deployer.ExtensibilityProviders.CSOM\PNP.Deployer.ExtensibilityProviders.CSOM.csproj", "{CA4E46F3-08F9-41E1-B90E-EDCDAD22EC22}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PNP.Deployer.ExtensibilityProviders.SSOM", "PNP.Deployer.ExtensibilityProviders.SSOM\PNP.Deployer.ExtensibilityProviders.SSOM.csproj", "{DC6CDDA2-4DB6-48A2-BBDB-9C05F84EB235}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PNP.Deployer.Common", "PNP.Deployer.Common\PNP.Deployer.Common.csproj", "{0FE76181-46FE-451B-B7A0-83E8B455DA5D}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C3C39474-E23C-4C7A-90C1-02153EC8E98A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C3C39474-E23C-4C7A-90C1-02153EC8E98A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C3C39474-E23C-4C7A-90C1-02153EC8E98A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C3C39474-E23C-4C7A-90C1-02153EC8E98A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CA4E46F3-08F9-41E1-B90E-EDCDAD22EC22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CA4E46F3-08F9-41E1-B90E-EDCDAD22EC22}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CA4E46F3-08F9-41E1-B90E-EDCDAD22EC22}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CA4E46F3-08F9-41E1-B90E-EDCDAD22EC22}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DC6CDDA2-4DB6-48A2-BBDB-9C05F84EB235}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DC6CDDA2-4DB6-48A2-BBDB-9C05F84EB235}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DC6CDDA2-4DB6-48A2-BBDB-9C05F84EB235}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DC6CDDA2-4DB6-48A2-BBDB-9C05F84EB235}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0FE76181-46FE-451B-B7A0-83E8B455DA5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0FE76181-46FE-451B-B7A0-83E8B455DA5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0FE76181-46FE-451B-B7A0-83E8B455DA5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0FE76181-46FE-451B-B7A0-83E8B455DA5D}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/App.config b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/App.config
new file mode 100644
index 000000000..d0ec3e815
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/App.config
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Deployer/Deployer.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Deployer/Deployer.cs
new file mode 100644
index 000000000..bb610d77e
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Deployer/Deployer.cs
@@ -0,0 +1,160 @@
+using NLog;
+using System;
+using System.IO;
+using System.Net;
+using System.Configuration;
+using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml;
+using PNP.Deployer.Common;
+
+// =======================================================
+///
+/// Simon-Pierre Plante (sp.plante@gmail.com)
+///
+// =======================================================
+namespace PNP.Deployer
+{
+ public class Deployer
+ {
+ #region Constants
+
+ private const string APP_SETTING_SEQUENCES_FILE = "clientSequencesFile";
+ private const string SEQUENCES_SCHEMA_FILE_NAME = "Sequences.xsd";
+ private const string ERROR_FOLDER_INVALID = "The specified working directory '{0}' does not exist";
+ private const string ERROR_SEQUENCES_NOT_FOUND = "Sequences configuration file was not found at path '{0}'";
+ private const string ERROR_SEQUENCES_INVALID = "The {0} file is invalid : {1}";
+
+ #endregion
+
+
+ #region Private Members
+
+ private static Logger logger = LogManager.GetCurrentClassLogger();
+
+ #endregion
+
+
+ #region Public Members
+
+ public string WorkingDirectory { get; set; }
+ public EnvironmentType Environment { get; set; }
+ public ICredentials Credentials { get; set; }
+ public SequencesConfiguration SequencesConfig { get; set; }
+
+ #endregion
+
+
+ #region Constructor
+
+ // ===========================================================================================================
+ ///
+ /// Initializes the deployer based on the specified arguments
+ ///
+ /// The working directory on which to map the deployer
+ /// The type of environment on which the deployer is executed
+ /// The credential that must be used the get the SharePoint context
+ // ===========================================================================================================
+ public Deployer(string WorkingDirectory, EnvironmentType Environment, ICredentials Credentials)
+ {
+ // --------------------------------------------------
+ // If the WorkingDirectory doesn't exist
+ // --------------------------------------------------
+ if (!Directory.Exists(WorkingDirectory))
+ throw new DeployerArgumentsException(String.Format(ERROR_FOLDER_INVALID, WorkingDirectory));
+
+ // --------------------------------------------------
+ // Initializes the deployer's arguments
+ // --------------------------------------------------
+ this.WorkingDirectory = WorkingDirectory;
+ logger.Info("Loaded 'WorkingDirectory' with value '{0}'", this.WorkingDirectory);
+
+ this.Environment = Environment;
+ logger.Info("Loaded 'EnvironmentType' with value '{0}'", this.Environment);
+
+ this.Credentials = Credentials;
+ logger.Info("Loaded 'Credentials' with success");
+ }
+
+ #endregion
+
+
+ #region Private Methods
+
+ // ===========================================================================================================
+ ///
+ /// Throws an exception if the sequences file is missing or invalid
+ ///
+ /// The Sequences.xml file path
+ // ===========================================================================================================
+ private void ValidateSequencesFile(string sequencesFilePath)
+ {
+ logger.Info("Validating the '{0}' file", Path.GetFileName(sequencesFilePath));
+
+ // --------------------------------------------------
+ // Throws an exception if Sequences.xml is not found
+ // --------------------------------------------------
+ if (!File.Exists(sequencesFilePath))
+ throw new FileNotFoundException(String.Format(ERROR_SEQUENCES_NOT_FOUND, sequencesFilePath));
+
+ // --------------------------------------------------
+ // Throws an exception if Sequences.xml is invalid
+ // --------------------------------------------------
+ XmlUtility.ValidateSchema(sequencesFilePath, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, SEQUENCES_SCHEMA_FILE_NAME));
+
+ logger.Info("File '{0}' has completed XSD validation with success", Path.GetFileName(sequencesFilePath));
+ }
+
+
+ // ===========================================================================================================
+ ///
+ /// Deserializes the sequences.xml file and stores a SequencesConfiguration object
+ ///
+ /// A SequencesConfiguration object
+ // ===========================================================================================================
+ private void LoadSequencesConfiguration()
+ {
+ // --------------------------------------------------
+ // Validates the deployer's sequences file
+ // --------------------------------------------------
+ string sequencesFilePath = Path.Combine(this.WorkingDirectory, ConfigurationManager.AppSettings[APP_SETTING_SEQUENCES_FILE]);
+ ValidateSequencesFile(sequencesFilePath);
+
+ // --------------------------------------------------
+ // Deserializes the Sequences.xml file
+ // --------------------------------------------------
+ this.SequencesConfig = XmlUtility.DeserializeXmlFile(sequencesFilePath);
+ }
+
+ #endregion
+
+
+ #region Public Methods
+
+ // ===========================================================================================================
+ ///
+ /// Launches the deployer
+ ///
+ // ===========================================================================================================
+ public void Launch()
+ {
+ // --------------------------------------------------
+ // Loads the sequences configuration
+ // --------------------------------------------------
+ LoadSequencesConfiguration();
+
+ // --------------------------------------------------
+ // Maps a template provider to the working directory
+ // --------------------------------------------------
+ XMLTemplateProvider provider = new XMLFileSystemTemplateProvider(this.WorkingDirectory, string.Empty);
+
+ // --------------------------------------------------
+ // Launches the sequences
+ // --------------------------------------------------
+ foreach (Sequence sequence in this.SequencesConfig.Sequences)
+ {
+ sequence.Launch(this.Credentials, provider);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Deployer/DeployerOptions.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Deployer/DeployerOptions.cs
new file mode 100644
index 000000000..fea765d91
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Deployer/DeployerOptions.cs
@@ -0,0 +1,117 @@
+using System;
+using System.Reflection;
+using CommandLine;
+using CommandLine.Text;
+
+
+// =======================================================
+///
+/// Simon-Pierre Plante (sp.plante@gmail.com)
+///
+// =======================================================
+namespace PNP.Deployer
+{
+ public class DeployerOptions
+ {
+ #region Constants
+
+ private const string HELP_ENVIRONMENT = "Whether the deployment occurs on a 'OnPrem' on 'Online' envrionment.";
+ private const string HELP_WORKING_DIRECTORY = "The working directory on which the deployer should be mapped to in order to deploy the artifacts.";
+ private const string HELP_PROMPT_CREDENTIALS = "Wheter there should be a prompt for credentials or not.";
+
+ #endregion
+
+
+ #region Public Members
+
+ // --------------------------------------------------
+ // The environment on which the deployer is executed
+ // --------------------------------------------------
+ [Option('e', "Environment", DefaultValue = EnvironmentType.OnPrem, HelpText = HELP_ENVIRONMENT, Required = false)]
+ public EnvironmentType Environment { get; set; }
+
+ // --------------------------------------------------
+ // The working directory that contains the artifacts
+ // --------------------------------------------------
+ [Option('w', "WorkingDirectory", HelpText = HELP_WORKING_DIRECTORY, Required = true)]
+ public string WorkingDirectory { get; set; }
+
+ // --------------------------------------------------
+ // Whether to prompt for specific credentials or not
+ // --------------------------------------------------
+ [Option('p', "PromptCredentials", DefaultValue = false, HelpText = HELP_PROMPT_CREDENTIALS, Required = false)]
+ public Boolean PromptCredentials { get; set; }
+
+ // --------------------------------------------------
+ // Stores the parser state for errors handling
+ // --------------------------------------------------
+ [ParserState]
+ public IParserState LastParserState { get; set; }
+
+ #endregion
+
+
+ #region Public Methods
+
+ // ===========================================================================================================
+ ///
+ /// Default 'help' screen implementation for the console
+ ///
+ /// The default help screen
+ // ===========================================================================================================
+ [HelpOption(HelpText = "Display this deployer help screen")]
+ public string GetUsage()
+ {
+
+ // --------------------------------------------------
+ // Initializes the HelpText object
+ // --------------------------------------------------
+ var help = new HelpText
+ {
+ AdditionalNewLineAfterOption = true,
+ AddDashesToOption = true
+ };
+
+ help.AddPreOptionsLine(string.Format(" PNP.Deployer (v.{0}) - By Simon-Pierre Plante", Assembly.GetExecutingAssembly().GetName().Version.ToString()));
+ help.AddPreOptionsLine("");
+ help.AddPreOptionsLine("");
+
+ // --------------------------------------------------
+ // Adds the error section if any
+ // --------------------------------------------------
+ if (this.LastParserState != null && this.LastParserState.Errors.Count > 0 )
+ {
+
+ var errors = help.RenderParsingErrorsText(this, 4);
+
+ if (!string.IsNullOrEmpty(errors))
+ {
+ help.AddPreOptionsLine(" Error(s):");
+ help.AddPreOptionsLine("");
+ help.AddPreOptionsLine(errors);
+ help.AddPreOptionsLine("");
+ }
+ }
+
+ // --------------------------------------------------
+ // Adds the Usage section
+ // --------------------------------------------------
+ help.AddPreOptionsLine(" Usage:");
+ help.AddPreOptionsLine("");
+ help.AddPreOptionsLine(" PNP.Deployer.exe --WorkingDirectory \"C:\\DeploymentFolder\"");
+ help.AddPreOptionsLine(" [--PromptCredentials] [--Environment \"OnPrem|Online\"]");
+ help.AddPreOptionsLine("");
+ help.AddPreOptionsLine("");
+
+ // --------------------------------------------------
+ // Adds the Options section
+ // --------------------------------------------------
+ help.AddPreOptionsLine(" Options: ");
+ help.AddOptions(this);
+
+ return help;
+ }
+
+ #endregion
+ }
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Enums/EnvironmentType.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Enums/EnvironmentType.cs
new file mode 100644
index 000000000..934ddb301
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Enums/EnvironmentType.cs
@@ -0,0 +1,13 @@
+// =======================================================
+///
+/// Simon-Pierre Plante (sp.plante@gmail.com)
+///
+// =======================================================
+namespace PNP.Deployer
+{
+ public enum EnvironmentType
+ {
+ Online,
+ OnPrem
+ }
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Enums/ExitCode.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Enums/ExitCode.cs
new file mode 100644
index 000000000..fd518ebe5
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Enums/ExitCode.cs
@@ -0,0 +1,13 @@
+// =======================================================
+///
+/// Simon-Pierre Plante (sp.plante@gmail.com)
+///
+// =======================================================
+namespace PNP.Deployer
+{
+ public enum ExitCode : int
+ {
+ Success = 0,
+ Failure = 1
+ }
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Exceptions/DeployerArgumentsException.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Exceptions/DeployerArgumentsException.cs
new file mode 100644
index 000000000..3f7cfddc9
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Exceptions/DeployerArgumentsException.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Runtime.Serialization;
+
+
+// =======================================================
+///
+/// Simon-Pierre Plante (sp.plante@gmail.com)
+///
+// =======================================================
+namespace PNP.Deployer
+{
+ public class DeployerArgumentsException : Exception
+ {
+ #region Constructors
+
+ public DeployerArgumentsException() : base() { }
+
+ public DeployerArgumentsException(String message) : base(message) { }
+
+ public DeployerArgumentsException(String message, Exception innerException) : base(message, innerException) { }
+
+ public DeployerArgumentsException(SerializationInfo info, StreamingContext context) : base(info, context) { }
+
+ #endregion
+ }
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Extensions/LoggerExtensions.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Extensions/LoggerExtensions.cs
new file mode 100644
index 000000000..dd9629bcb
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Extensions/LoggerExtensions.cs
@@ -0,0 +1,79 @@
+using NLog;
+using System;
+
+
+// =======================================================
+///
+/// Simon-Pierre Plante (sp.plante@gmail.com)
+///
+// =======================================================
+namespace PNP.Deployer
+{
+ public static class LoggerExtensions
+ {
+ #region Public Methods
+
+ // ==============================================================================================================
+ ///
+ /// Logs the specified message as a more visible "section"
+ ///
+ /// The current Logger
+ /// The message that needs to be logged as a "section"
+ /// The LogLevel in which the section needs to be logged
+ // ==============================================================================================================
+ public static void Section(this Logger logger, String message, LogLevel logLevel)
+ {
+ logger.Log(logLevel, "================================================");
+ logger.Log(logLevel, message);
+ logger.Log(logLevel, "================================================");
+ }
+
+
+ // ==============================================================================================================
+ ///
+ /// Logs the specified message as a more visible "section"
+ ///
+ /// The current Logger
+ /// The message that needs to be logged as a "section"
+ /// The LogLevel in which the section needs to be logged
+ /// An object array that contains zero or more objects to format
+ // ==============================================================================================================
+ public static void Section(this Logger logger, String message, LogLevel logLevel, params object[] args)
+ {
+ logger.Section(String.Format(message, args), logLevel);
+ }
+
+
+ // ==============================================================================================================
+ ///
+ /// Logs the specified message as a more visible "section"
+ ///
+ /// The current Logger
+ /// The message that needs to be logged as a "section"
+ /// The LogLevel in which the sub section needs to be logged
+ // ==============================================================================================================
+ public static void SubSection(this Logger logger, String message, LogLevel logLevel)
+ {
+ logger.Log(logLevel, "-----------------------------------");
+ logger.Log(logLevel, message);
+ logger.Log(logLevel, "-----------------------------------");
+ }
+
+
+ // ==============================================================================================================
+ ///
+ /// Logs the specified message as a more visible "section"
+ ///
+ /// The current Logger
+ /// The message that needs to be logged as a "section"
+ /// The LogLevel in which the sub section needs to be logged
+ /// An object array that contains zero or more objects to format
+ // ==============================================================================================================
+ public static void SubSection(this Logger logger, String message, LogLevel logLevel, params object[] args)
+ {
+ logger.SubSection(String.Format(message,args), logLevel);
+ }
+
+ #endregion
+ }
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Extensions/StringExtensions.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Extensions/StringExtensions.cs
new file mode 100644
index 000000000..e2b72f692
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Extensions/StringExtensions.cs
@@ -0,0 +1,36 @@
+using System.Security;
+
+
+// =======================================================
+///
+/// Simon-Pierre Plante (sp.plante@gmail.com)
+///
+// =======================================================
+namespace PNP.Deployer
+{
+ public static class StringExtensions
+ {
+ #region Public Methods
+
+ // ===========================================================================================================
+ ///
+ /// Converts the current String into a SecureString object
+ ///
+ /// The current String
+ /// The current string converted as a SecureString object
+ // ===========================================================================================================
+ public static SecureString ToSecureString(this string str)
+ {
+ SecureString ss = new SecureString();
+
+ foreach(char c in str.ToCharArray())
+ {
+ ss.AppendChar(c);
+ }
+
+ return ss;
+ }
+
+ #endregion
+ }
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Helpers/ConsoleUtility.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Helpers/ConsoleUtility.cs
new file mode 100644
index 000000000..ffc4c8158
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Helpers/ConsoleUtility.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Security;
+
+
+// =======================================================
+///
+/// Simon-Pierre Plante (sp.plante@gmail.com)
+///
+// =======================================================
+namespace PNP.Deployer
+{
+ public static class ConsoleUtility
+ {
+ #region Private Methods
+
+ // ===========================================================================================================
+ ///
+ /// Prompts the user with the specified label and returns it's input
+ ///
+ /// The label that will be prompted to the user
+ /// Whether the input should be of type 'password' or not
+ /// The user's input as a String
+ // ===========================================================================================================
+ private static string GetInput(string label, bool isPassword)
+ {
+ ConsoleColor defaultForeground = Console.ForegroundColor;
+ Console.ForegroundColor = ConsoleColor.DarkCyan;
+ Console.Write("{0} : ", label);
+ Console.ForegroundColor = defaultForeground;
+
+ string value = "";
+
+ for (ConsoleKeyInfo keyInfo = Console.ReadKey(true); keyInfo.Key != ConsoleKey.Enter; keyInfo = Console.ReadKey(true))
+ {
+ if (keyInfo.Key == ConsoleKey.Backspace)
+ {
+ if (value.Length > 0)
+ {
+ value = value.Remove(value.Length - 1);
+ Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
+ Console.Write(" ");
+ Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
+ }
+ }
+ else if (keyInfo.Key != ConsoleKey.Enter)
+ {
+ if (isPassword)
+ {
+ Console.Write("*");
+ }
+ else
+ {
+ Console.Write(keyInfo.KeyChar);
+ }
+ value += keyInfo.KeyChar;
+ }
+ }
+ Console.WriteLine("");
+
+ return value;
+ }
+
+ #endregion
+
+
+ #region Public Methods
+
+ // ===========================================================================================================
+ ///
+ /// Prompts the user with the specified label and returns it's clear text answer
+ ///
+ /// The label that will be prompted to the user
+ /// The user's input as a clear string object
+ // ===========================================================================================================
+ public static string GetInputAsText(string label)
+ {
+ return GetInput(label, false);
+ }
+
+
+ // ===========================================================================================================
+ ///
+ /// Prompts the user with the specified label and returns it's secure answer
+ ///
+ /// The label that will be prompted to the user
+ /// The user's input as a SecureString object
+ // ===========================================================================================================
+ public static SecureString GetInputAsSecureString(string label)
+ {
+ string input = GetInput(label, true);
+ return input.ToSecureString();
+ }
+
+ #endregion
+ }
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Helpers/FilesUtility.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Helpers/FilesUtility.cs
new file mode 100644
index 000000000..72c7460b1
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Helpers/FilesUtility.cs
@@ -0,0 +1,69 @@
+using System.IO;
+
+
+namespace PNP.Deployer
+{
+ // =======================================================
+ ///
+ /// Simon-Pierre Plante (sp.plante@gmail.com)
+ ///
+ // =======================================================
+ public static class FilesUtility
+ {
+ #region Private Methods
+
+ // ===========================================================================================================
+ ///
+ /// Copies recursively all files/folders from the specified source folder to the specified target folder
+ ///
+ /// The DirectoryInfo of the source folder
+ /// The DirectoryInfo of the target folder
+ // ===========================================================================================================
+ private static void CopyDirectoryRecursive(DirectoryInfo source, DirectoryInfo target)
+ {
+ // --------------------------------------------------
+ // Creates the destination folder if needed
+ // --------------------------------------------------
+ if (!target.Exists)
+ Directory.CreateDirectory(target.FullName);
+
+ // --------------------------------------------------
+ // Copies each file in the destination folder
+ // --------------------------------------------------
+ foreach(FileInfo fileInfo in source.GetFiles())
+ {
+ fileInfo.CopyTo(Path.Combine(target.FullName, fileInfo.Name), true);
+ }
+
+ // --------------------------------------------------
+ // Copies each folder in the destination folder
+ // --------------------------------------------------
+ foreach (DirectoryInfo sourceSubDirInfo in source.GetDirectories())
+ {
+ DirectoryInfo targetSubDirInfo = target.CreateSubdirectory(sourceSubDirInfo.Name);
+ CopyDirectoryRecursive(sourceSubDirInfo, targetSubDirInfo);
+ }
+ }
+
+ #endregion
+
+
+ #region Public Methods
+
+ // ===========================================================================================================
+ ///
+ /// Copies recursively all files/folders from the specified source folder to the specified target folder
+ ///
+ /// The source directory that needs to be copied
+ /// The target directory
+ // ===========================================================================================================
+ public static void CopyDirectory(string sourceDirectory, string destinationDirectory)
+ {
+ DirectoryInfo infoSourceDirectory = new DirectoryInfo(sourceDirectory);
+ DirectoryInfo infoDestinationDirectory = new DirectoryInfo(destinationDirectory);
+ CopyDirectoryRecursive(infoSourceDirectory, infoDestinationDirectory);
+ }
+
+ #endregion
+ }
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Sequences/Sequence.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Sequences/Sequence.cs
new file mode 100644
index 000000000..427b20f29
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Sequences/Sequence.cs
@@ -0,0 +1,96 @@
+using NLog;
+using System;
+using System.Net;
+using System.Threading;
+using System.Xml.Serialization;
+using System.Collections.Generic;
+using Microsoft.SharePoint.Client;
+using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml;
+
+
+// =======================================================
+///
+/// Simon-Pierre Plante (sp.plante@gmail.com)
+///
+// =======================================================
+namespace PNP.Deployer
+{
+ [Serializable()]
+ public class Sequence
+ {
+ #region Private Members
+
+ private static Logger logger = LogManager.GetCurrentClassLogger();
+
+ #endregion
+
+
+ #region Public Members
+
+ [XmlAttribute("name")]
+ public string Name { get; set; }
+
+ [XmlAttribute("description")]
+ public string Description { get; set; }
+
+ [XmlAttribute("webUrl")]
+ public string WebUrl { get; set; }
+
+ [XmlAttribute("ignore")]
+ public bool Ignore { get; set; }
+
+ [XmlArray("templates")]
+ [XmlArrayItem("template", typeof(Template))]
+ public List Templates { get; set; }
+
+ #endregion
+
+
+ #region Public Methods
+
+ // ===========================================================================================================
+ ///
+ /// Launches the current sequence by executing all it's templates
+ ///
+ /// The credentials required for creating the client context
+ /// The XMLTemplatePRovider that is mapped to the client's working directory
+ // ===========================================================================================================
+ public void Launch(ICredentials credentials, XMLTemplateProvider templateProvider)
+ {
+ if(!this.Ignore)
+ {
+ logger.Info("Launching sequence '{0}' ({1})", this.Name, this.Description);
+
+ using (ClientContext ctx = new ClientContext(this.WebUrl))
+ {
+ // --------------------------------------------------
+ // Sets the context with the provided credentials
+ // --------------------------------------------------
+ ctx.Credentials = credentials;
+ ctx.RequestTimeout = Timeout.Infinite;
+
+ // --------------------------------------------------
+ // Loads the full web for futur references (providers)
+ // --------------------------------------------------
+ Web web = ctx.Web;
+ ctx.Load(web);
+ ctx.ExecuteQueryRetry();
+
+ // --------------------------------------------------
+ // Launches the templates
+ // --------------------------------------------------
+ foreach (Template template in this.Templates)
+ {
+ template.Apply(web, templateProvider);
+ }
+ }
+ }
+ else
+ {
+ logger.Info("Ignoring sequence '{0}' ({1})", this.Name, this.Description);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Sequences/SequencesConfiguration.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Sequences/SequencesConfiguration.cs
new file mode 100644
index 000000000..68c6590b7
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Sequences/SequencesConfiguration.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Serialization;
+
+// =======================================================
+///
+/// Simon-Pierre Plante (sp.plante@gmail.com)
+///
+// =======================================================
+namespace PNP.Deployer
+{
+ [Serializable()]
+ [XmlRoot("sequencesConfiguration")]
+ public class SequencesConfiguration
+ {
+ #region Public Members
+
+ [XmlArray("sequences")]
+ [XmlArrayItem("sequence", typeof(Sequence))]
+ public List Sequences { get; set; }
+
+ #endregion
+ }
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Sequences/Template.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Sequences/Template.cs
new file mode 100644
index 000000000..3884f8443
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Sequences/Template.cs
@@ -0,0 +1,216 @@
+using NLog;
+using System;
+using System.IO;
+using System.Xml.Serialization;
+using Microsoft.SharePoint.Client;
+using OfficeDevPnP.Core.Framework.Provisioning.Model;
+using OfficeDevPnP.Core.Framework.Provisioning.ObjectHandlers;
+using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml;
+using OfficeDevPnP.Core.Framework.Provisioning.Connectors;
+using System.Collections.Generic;
+
+
+// =======================================================
+///
+/// Simon-Pierre Plante (sp.plante@gmail.com)
+///
+// =======================================================
+namespace PNP.Deployer
+{
+ [Serializable()]
+ public class Template
+ {
+ #region Constants
+
+ private const string PARAMETER_CONNECTION_STRING = "ConnectionString";
+ private const string ERROR_TEMPLATE_NOT_FOUND = "Template '{0}' was not found";
+ private const string EXTENSION_PNP_FILE = ".pnp";
+
+ #endregion
+
+
+ #region Private Members
+
+ private static Logger logger = LogManager.GetCurrentClassLogger();
+
+ #endregion
+
+
+ #region Public Members
+
+ [XmlAttribute("name")]
+ public string Name { get; set; }
+
+ [XmlAttribute("path")]
+ public string Path { get; set; }
+
+ [XmlAttribute("ignore")]
+ public bool Ignore { get; set; }
+
+ #endregion
+
+
+ #region Private Methods
+
+ // ===========================================================================================================
+ ///
+ /// Returns whether the current template file exists based on the specified template provider
+ ///
+ /// The template provider that is mapped to the client's working directory
+ /// True if the template file exists, otherwise false
+ // ===========================================================================================================
+ private bool TemplateExists(XMLTemplateProvider templateProvider)
+ {
+ string workingDirectory = templateProvider.Connector.Parameters[PARAMETER_CONNECTION_STRING].ToString();
+ string templatePath = System.IO.Path.Combine(workingDirectory, this.Path);
+ return System.IO.File.Exists(templatePath);
+ }
+
+
+ // ===========================================================================================================
+ ///
+ /// Checks whether the current template is a packaged ".pnp" file or a regular ".xml" file
+ ///
+ /// Returns true if it's a packaged ".pnp" file, otherwise false
+ // ===========================================================================================================
+ private bool IsOpenXml()
+ {
+ return this.Path.ToLower().Trim().EndsWith(EXTENSION_PNP_FILE);
+ }
+
+
+ // ===========================================================================================================
+ ///
+ /// Returns the provisioning template applying information
+ ///
+ /// A ProvisioningTemplateApplyingInformation object
+ // ===========================================================================================================
+ private ProvisioningTemplateApplyingInformation GetTemplateApplyInfo()
+ {
+ ProvisioningTemplateApplyingInformation ptai = new ProvisioningTemplateApplyingInformation();
+ ptai.ProgressDelegate = delegate (string message, int progress, int total)
+ {
+ logger.Info("Executing step {0:00}/{1:00} - {2}", progress, total, message);
+ };
+
+ return ptai;
+ }
+
+
+ // ===========================================================================================================
+ ///
+ /// Applies the current template as a regular XML template on the specified web
+ ///
+ /// The Web on which to apply the template
+ /// The XMLTemplateProvider that is mapped to the client's working directory
+ // ===========================================================================================================
+ private void ApplyRegularXML(Web web, XMLTemplateProvider templateProvider)
+ {
+ logger.Info("Applying template '{0}' from file '{1}'", this.Name, this.Path);
+
+ // --------------------------------------------------
+ // Formats the template's execution rendering
+ // --------------------------------------------------
+ ProvisioningTemplateApplyingInformation ptai = GetTemplateApplyInfo();
+
+ // --------------------------------------------------
+ // Loads the template
+ // --------------------------------------------------
+ ProvisioningTemplate template = templateProvider.GetTemplate(this.Path);
+ template.Connector = templateProvider.Connector;
+
+ // --------------------------------------------------
+ // Applies the template
+ // --------------------------------------------------
+ web.ApplyProvisioningTemplate(template, ptai);
+ }
+
+
+ // ===========================================================================================================
+ ///
+ /// Applies the current template as an open XML ".pnp" package on the specified web
+ ///
+ /// The Web on which to apply the template
+ /// The XMLTemplateProvider that is mapped to the client's working directory
+ // ===========================================================================================================
+ private void ApplyOpenXML(Web web, XMLTemplateProvider templateProvider)
+ {
+ logger.Info("Applying open XML package '{0}' from file '{1}'", this.Name, this.Path);
+
+ // --------------------------------------------------
+ // Formats the template's execution rendering
+ // --------------------------------------------------
+ ProvisioningTemplateApplyingInformation ptai = GetTemplateApplyInfo();
+
+ // --------------------------------------------------
+ // Replaces the regular provider by an OpenXml one
+ // --------------------------------------------------
+ string workingDirectory = templateProvider.Connector.Parameters[PARAMETER_CONNECTION_STRING].ToString();
+ FileSystemConnector fileSystemConnector = new FileSystemConnector(workingDirectory, "");
+ OpenXMLConnector openXmlConnector = new OpenXMLConnector(this.Path, fileSystemConnector);
+ XMLTemplateProvider openXmlTemplateProvider = new XMLOpenXMLTemplateProvider(openXmlConnector);
+
+ // --------------------------------------------------
+ // Loops through all templates within the .pnp package
+ // --------------------------------------------------
+ List templates = openXmlTemplateProvider.GetTemplates();
+
+ foreach (ProvisioningTemplate template in templates)
+ {
+ logger.Info("Applying template '{0}' from file '{1}'", template.Id, this.Path);
+
+ // --------------------------------------------------
+ // Applies the template
+ // --------------------------------------------------
+ template.Connector = openXmlTemplateProvider.Connector;
+ web.ApplyProvisioningTemplate(template, ptai);
+ }
+ }
+
+ #endregion
+
+
+ #region Public Methods
+
+ // ===========================================================================================================
+ ///
+ /// Applies the current template on the specified Web object
+ ///
+ /// The Web object on which the template needs to be applied
+ /// The XMLTemplatePRovider that is mapped to the client's working directory
+ // ===========================================================================================================
+ public void Apply(Web web, XMLTemplateProvider templateProvider)
+ {
+ if(!this.Ignore)
+ {
+ if (TemplateExists(templateProvider))
+ {
+ if (IsOpenXml())
+ {
+ // --------------------------------------------------
+ // Handles ".pnp" templates
+ // --------------------------------------------------
+ ApplyOpenXML(web, templateProvider);
+ }
+ else
+ {
+ // --------------------------------------------------
+ // Handles regular ".xml" templates
+ // --------------------------------------------------
+ ApplyRegularXML(web, templateProvider);
+ }
+ }
+ else
+ {
+ throw new FileNotFoundException(string.Format(ERROR_TEMPLATE_NOT_FOUND, System.IO.Path.Combine(templateProvider.Connector.Parameters[PARAMETER_CONNECTION_STRING].ToString(), this.Path)));
+ }
+ }
+ else
+ {
+ logger.Info("Ignoring template '{0}' from file '{1}'", this.Name, this.Path);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Tokenizer/Tokenizer.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Tokenizer/Tokenizer.cs
new file mode 100644
index 000000000..d44d377ab
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Tokenizer/Tokenizer.cs
@@ -0,0 +1,245 @@
+using NLog;
+using System;
+using System.IO;
+using System.Linq;
+using System.Configuration;
+using System.Collections.Generic;
+using PNP.Deployer.Common;
+
+
+namespace PNP.Deployer
+{
+ // =======================================================
+ ///
+ /// Simon-Pierre Plante (sp.plante@gmail.com)
+ ///
+ // =======================================================
+ public class Tokenizer
+ {
+ #region Constants
+
+ private const string ERROR_FOLDER_NOT_FOUND = "Unable to tokenize the specified folder '{0}' : Folder not found.";
+ private const string ERROR_FILE_NOT_FOUND = "Unable to tokenize the specified file '{0}' : File not found.";
+ private const string ERROR_TOKENS_NOT_FOUND = "Unable to tokenize the specified folder with the tokens file '{0}' : File not found.";
+ private const string TOKENS_SCHEMA_FILE_NAME = "TokensConfiguration.xsd";
+ private const string TOKENIZED_FOLDER_EXTENSION = "_Tokenized";
+ private const string DEFAULT_TOKENS_PREFIX = "token-";
+
+ #endregion
+
+
+ #region Private Members
+
+ private static Logger logger = LogManager.GetCurrentClassLogger();
+
+ #endregion
+
+
+ #region Public Members
+
+ public List Tokens = new List();
+
+ #endregion
+
+
+ #region Constructors
+
+ public Tokenizer(string tokensFilePath)
+ {
+ // --------------------------------------------------
+ // Loads the 'default' tokens from app.config
+ // --------------------------------------------------
+ List appSettingsTokens = LoadTokensFromAppSettings();
+ this.Tokens.AddRange(appSettingsTokens);
+ logger.Info("Loaded '{0}' tokens from app settings", appSettingsTokens.Count);
+
+ // --------------------------------------------------
+ // Loads tokens from the client tokens file
+ // --------------------------------------------------
+ List fileTokens = LoadTokensFromFile(tokensFilePath);
+ this.Tokens.AddRange(fileTokens);
+ logger.Info("Loaded '{0}' tokens from file '{1}'", fileTokens.Count, tokensFilePath);
+ }
+
+ #endregion
+
+
+ #region Private Methods
+
+ // ===========================================================================================================
+ ///
+ ///
+ ///
+ ///
+ // ===========================================================================================================
+ private List LoadTokensFromAppSettings()
+ {
+ List tokens = new List();
+
+ if(ConfigurationManager.AppSettings != null)
+ {
+ foreach (string key in ConfigurationManager.AppSettings.AllKeys.Where(x => x.StartsWith(DEFAULT_TOKENS_PREFIX)))
+ {
+ tokens.Add(new Token() { Key = key, Value = ConfigurationManager.AppSettings[key] });
+ }
+ }
+
+ return tokens;
+ }
+
+ // ===========================================================================================================
+ ///
+ /// Deserializes the specified tokens file if available and returns a list of Token objects
+ ///
+ ///
+ /// A list of Token objects
+ // ===========================================================================================================
+ private List LoadTokensFromFile(string tokensFilePath)
+ {
+ List tokens = new List();
+
+ if (File.Exists(tokensFilePath))
+ {
+ // --------------------------------------------------
+ // Validates the tokens file schema by XSD
+ // --------------------------------------------------
+ ValidateTokensFile(tokensFilePath);
+
+ // --------------------------------------------------
+ // Deserializes the tokens file
+ // --------------------------------------------------
+ TokensConfiguration tokensConfig = XmlUtility.DeserializeXmlFile(tokensFilePath);
+
+ if (tokensConfig != null && tokensConfig.Tokens != null && tokensConfig.Tokens.Count > 0)
+ {
+ tokens = tokensConfig.Tokens;
+ }
+ }
+
+ return tokens;
+ }
+
+
+ // ===========================================================================================================
+ ///
+ /// Throws an exception if the tokens file is invalid
+ ///
+ /// The tokens file path
+ // ===========================================================================================================
+ private void ValidateTokensFile(string tokensFilePath)
+ {
+ logger.Info("Validating the '{0}' file", Path.GetFileName(tokensFilePath));
+
+ // --------------------------------------------------
+ // Throws an exception if tokens file is invalid
+ // --------------------------------------------------
+ XmlUtility.ValidateSchema(tokensFilePath, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, TOKENS_SCHEMA_FILE_NAME));
+
+ logger.Info("File '{0}' has completed XSD validation with success", Path.GetFileName(tokensFilePath));
+ }
+
+
+ // ===========================================================================================================
+ ///
+ /// Tokenizes the specified folder and it's files/subfolders recursively
+ ///
+ /// The path of the folder that needs to be tokenized
+ // ===========================================================================================================
+ private void TokenizeFolderRecursive(string folderPath)
+ {
+ DirectoryInfo infoFolder = new DirectoryInfo(folderPath);
+
+ // --------------------------------------------------
+ // Tokenizes each file in the folder
+ // --------------------------------------------------
+ foreach (FileInfo fileInfo in infoFolder.GetFiles())
+ {
+ TokenizeFile(fileInfo.FullName);
+ }
+
+ // --------------------------------------------------
+ // Tokenizes each subfolder in the folder
+ // --------------------------------------------------
+ foreach (DirectoryInfo dirInfo in infoFolder.GetDirectories())
+ {
+ TokenizeFolderRecursive(dirInfo.FullName);
+ }
+ }
+
+ #endregion
+
+
+ #region Public Methods
+
+ // ===========================================================================================================
+ ///
+ /// Creates a tokenized version of the specified folder based on the available tokens
+ ///
+ /// The path of the folder that needs to be tokenized
+ /// The path of the tokenized folder
+ // ===========================================================================================================
+ public string TokenizeFolder(string folderPath)
+ {
+ string tokenizedFolderPath = null;
+
+ if (this.Tokens.Count > 0)
+ {
+ logger.Info("Tokenizing folder '{0}' to destination '{0}{1}'", Path.GetFileName(folderPath), TOKENIZED_FOLDER_EXTENSION);
+
+ // --------------------------------------------------
+ // Validates the source folder
+ // --------------------------------------------------
+ if (!Directory.Exists(folderPath))
+ throw new DirectoryNotFoundException(string.Format(ERROR_FOLDER_NOT_FOUND, folderPath));
+
+ // --------------------------------------------------
+ // Copies the source folder to it's tokenized destination
+ // --------------------------------------------------
+ tokenizedFolderPath = folderPath + TOKENIZED_FOLDER_EXTENSION;
+ FilesUtility.CopyDirectory(folderPath, tokenizedFolderPath);
+
+ // --------------------------------------------------
+ // Tokenizes the destination folder recursively
+ // --------------------------------------------------
+ TokenizeFolderRecursive(tokenizedFolderPath);
+
+ logger.Info("Folder '{0}' tokenized with success to destination '{0}{1}'", Path.GetFileName(folderPath), TOKENIZED_FOLDER_EXTENSION);
+ }
+
+ return tokenizedFolderPath;
+ }
+
+
+ // ===========================================================================================================
+ ///
+ /// Tokenizes the specified file based on the available tokens
+ ///
+ /// The path of the file that needs to be tokenized
+ // ===========================================================================================================
+ public void TokenizeFile(string filePath)
+ {
+ // --------------------------------------------------
+ // Validates the specified file
+ // --------------------------------------------------
+ if (!File.Exists(filePath))
+ throw new FileNotFoundException(string.Format(ERROR_FILE_NOT_FOUND, filePath));
+
+ if (!filePath.ToLower().Trim().EndsWith(".pnp"))
+ {
+ // --------------------------------------------------
+ // Replaces the tokens by their value
+ // --------------------------------------------------
+ string fileContent = File.ReadAllText(filePath);
+
+ foreach (Token token in this.Tokens)
+ {
+ fileContent = fileContent.Replace("{{" + token.Key + "}}", token.Value);
+ }
+
+ File.WriteAllText(filePath, fileContent);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Tokenizer/TokensConfiguration.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Tokenizer/TokensConfiguration.cs
new file mode 100644
index 000000000..180fe1f8a
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/Tokenizer/TokensConfiguration.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Xml.Serialization;
+using System.Collections.Generic;
+
+
+// =======================================================
+///
+/// Simon-Pierre Plante (sp.plante@gmail.com)
+///
+// =======================================================
+namespace PNP.Deployer
+{
+ [Serializable()]
+ [XmlRoot("tokensConfiguration")]
+ public class TokensConfiguration
+ {
+ #region Public Members
+
+ [XmlArray("tokens")]
+ [XmlArrayItem("add", typeof(Token))]
+ public List Tokens { get; set; }
+
+ #endregion
+ }
+
+
+ [Serializable()]
+ public struct Token
+ {
+ #region Public Members
+
+ [XmlAttribute("key")]
+ public string Key { get; set; }
+
+ [XmlAttribute("value")]
+ public string Value { get; set; }
+
+ #endregion
+ }
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/XSD/Sequences.xsd b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/XSD/Sequences.xsd
new file mode 100644
index 000000000..7fa332d28
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/XSD/Sequences.xsd
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/XSD/TokensConfiguration.xsd b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/XSD/TokensConfiguration.xsd
new file mode 100644
index 000000000..3ee452ebe
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Classes/XSD/TokensConfiguration.xsd
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/NLog.config b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/NLog.config
new file mode 100644
index 000000000..9095eaf26
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/NLog.config
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/NLog.xsd b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/NLog.xsd
new file mode 100644
index 000000000..a6f5a5238
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/NLog.xsd
@@ -0,0 +1,2612 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Watch config file for changes and reload automatically.
+
+
+
+
+ Print internal NLog messages to the console. Default value is: false
+
+
+
+
+ Print internal NLog messages to the console error output. Default value is: false
+
+
+
+
+ Write internal NLog messages to the specified file.
+
+
+
+
+ Log level threshold for internal log messages. Default value is: Info.
+
+
+
+
+ Global log level threshold for application log messages. Messages below this level won't be logged..
+
+
+
+
+ Pass NLog internal exceptions to the application. Default value is: false.
+
+
+
+
+ Write internal NLog messages to the the System.Diagnostics.Trace. Default value is: false
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Make all targets within this section asynchronous (creates additional threads but the calling thread isn't blocked by any target writes).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Prefix for targets/layout renderers/filters/conditions loaded from this assembly.
+
+
+
+
+ Load NLog extensions from the specified file (*.dll)
+
+
+
+
+ Load NLog extensions from the specified assembly. Assembly name should be fully qualified.
+
+
+
+
+
+
+
+
+
+ Name of the logger. May include '*' character which acts like a wildcard. Allowed forms are: *, Name, *Name, Name* and *Name*
+
+
+
+
+ Comma separated list of levels that this rule matches.
+
+
+
+
+ Minimum level that this rule matches.
+
+
+
+
+ Maximum level that this rule matches.
+
+
+
+
+ Level that this rule matches.
+
+
+
+
+ Comma separated list of target names.
+
+
+
+
+ Ignore further rules if this one matches.
+
+
+
+
+ Enable or disable logging rule. Disabled rules are ignored.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the file to be included. The name is relative to the name of the current config file.
+
+
+
+
+ Ignore any errors in the include file.
+
+
+
+
+
+
+ Variable name.
+
+
+
+
+ Variable value.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Layout used to format log messages.
+
+
+
+
+ Indicates whether to add <!-- --> comments around all written texts.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Number of log events that should be processed in a batch by the lazy writer thread.
+
+
+
+
+ Action to be taken when the lazy writer thread request queue count exceeds the set limit.
+
+
+
+
+ Limit on the number of requests in the lazy writer thread request queue.
+
+
+
+
+ Time in milliseconds to sleep between batches.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Number of log events to be buffered.
+
+
+
+
+ Timeout (in milliseconds) after which the contents of buffer will be flushed if there's no write in the specified period of time. Use -1 to disable timed flushes.
+
+
+
+
+ Indicates whether to use sliding timeout.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Encoding to be used.
+
+
+
+
+ Instance of that is used to format log messages.
+
+
+
+
+ Maximum message size in bytes.
+
+
+
+
+ Indicates whether to append newline at the end of log message.
+
+
+
+
+ Action that should be taken if the will be more connections than .
+
+
+
+
+ Action that should be taken if the message is larger than maxMessageSize.
+
+
+
+
+ Indicates whether to keep connection open whenever possible.
+
+
+
+
+ Size of the connection cache (number of connections which are kept alive).
+
+
+
+
+ Maximum current connections. 0 = no maximum.
+
+
+
+
+ Network address.
+
+
+
+
+ Maximum queue size.
+
+
+
+
+ Indicates whether to include source info (file name and line number) in the information sent over the network.
+
+
+
+
+ NDC item separator.
+
+
+
+
+ Indicates whether to include stack contents.
+
+
+
+
+ Indicates whether to include call site (class and method name) in the information sent over the network.
+
+
+
+
+ AppInfo field. By default it's the friendly name of the current AppDomain.
+
+
+
+
+ Indicates whether to include NLog-specific extensions to log4j schema.
+
+
+
+
+ Indicates whether to include dictionary contents.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Layout that should be use to calcuate the value for the parameter.
+
+
+
+
+ Viewer parameter name.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Text to be rendered.
+
+
+
+
+ Header.
+
+
+
+
+ Footer.
+
+
+
+
+ Indicates whether to use default row highlighting rules.
+
+
+
+
+ The encoding for writing messages to the .
+
+
+
+
+ Indicates whether the error stream (stderr) should be used instead of the output stream (stdout).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Condition that must be met in order to set the specified foreground and background color.
+
+
+
+
+ Background color.
+
+
+
+
+ Foreground color.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Indicates whether to ignore case when comparing texts.
+
+
+
+
+ Regular expression to be matched. You must specify either text or regex.
+
+
+
+
+ Text to be matched. You must specify either text or regex.
+
+
+
+
+ Indicates whether to match whole words only.
+
+
+
+
+ Compile the ? This can improve the performance, but at the costs of more memory usage. If false, the Regex Cache is used.
+
+
+
+
+ Background color.
+
+
+
+
+ Foreground color.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Text to be rendered.
+
+
+
+
+ Header.
+
+
+
+
+ Footer.
+
+
+
+
+ Indicates whether to send the log messages to the standard error instead of the standard output.
+
+
+
+
+ The encoding for writing messages to the .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Connection string. When provided, it overrides the values specified in DBHost, DBUserName, DBPassword, DBDatabase.
+
+
+
+
+ Name of the connection string (as specified in <connectionStrings> configuration section.
+
+
+
+
+ Database name. If the ConnectionString is not provided this value will be used to construct the "Database=" part of the connection string.
+
+
+
+
+ Database host name. If the ConnectionString is not provided this value will be used to construct the "Server=" part of the connection string.
+
+
+
+
+ Database password. If the ConnectionString is not provided this value will be used to construct the "Password=" part of the connection string.
+
+
+
+
+ Name of the database provider.
+
+
+
+
+ Database user name. If the ConnectionString is not provided this value will be used to construct the "User ID=" part of the connection string.
+
+
+
+
+ Indicates whether to keep the database connection open between the log events.
+
+
+
+
+ Obsolete - value will be ignored! The logging code always runs outside of transaction. Gets or sets a value indicating whether to use database transactions. Some data providers require this.
+
+
+
+
+ Connection string using for installation and uninstallation. If not provided, regular ConnectionString is being used.
+
+
+
+
+ Text of the SQL command to be run on each log level.
+
+
+
+
+ Type of the SQL command to be run on each log level.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Type of the command.
+
+
+
+
+ Connection string to run the command against. If not provided, connection string from the target is used.
+
+
+
+
+ Indicates whether to ignore failures.
+
+
+
+
+ Command text.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Layout that should be use to calcuate the value for the parameter.
+
+
+
+
+ Database parameter name.
+
+
+
+
+ Database parameter precision.
+
+
+
+
+ Database parameter scale.
+
+
+
+
+ Database parameter size.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Text to be rendered.
+
+
+
+
+ Header.
+
+
+
+
+ Footer.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Layout used to format log messages.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Layout used to format log messages.
+
+
+
+
+ Layout that renders event Category.
+
+
+
+
+ Layout that renders event ID.
+
+
+
+
+ Name of the Event Log to write to. This can be System, Application or any user-defined name.
+
+
+
+
+ Name of the machine on which Event Log service is running.
+
+
+
+
+ Value to be used as the event Source.
+
+
+
+
+ Action to take if the message is larger than the option.
+
+
+
+
+ Optional entrytype. When not set, or when not convertable to then determined by
+
+
+
+
+ Message length limit to write to the Event Log.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Indicates whether to return to the first target after any successful write.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Text to be rendered.
+
+
+
+
+ Header.
+
+
+
+
+ Footer.
+
+
+
+
+ File encoding.
+
+
+
+
+ Line ending mode.
+
+
+
+
+ Way file archives are numbered.
+
+
+
+
+ Name of the file to be used for an archive.
+
+
+
+
+ Indicates whether to automatically archive log files every time the specified time passes.
+
+
+
+
+ Size in bytes above which log files will be automatically archived. Warning: combining this with isn't supported. We cannot create multiple archive files, if they should have the same name. Choose:
+
+
+
+
+ Indicates whether to compress archive files into the zip archive format.
+
+
+
+
+ Maximum number of archive files that should be kept.
+
+
+
+
+ Gets or set a value indicating whether a managed file stream is forced, instead of used the native implementation.
+
+
+
+
+ Cleanup invalid values in a filename, e.g. slashes in a filename. If set to true, this can impact the performance of massive writes. If set to false, nothing gets written when the filename is wrong.
+
+
+
+
+ Name of the file to write to.
+
+
+
+
+ Value specifying the date format to use when archiving files.
+
+
+
+
+ Indicates whether to archive old log file on startup.
+
+
+
+
+ Indicates whether to create directories if they do not exist.
+
+
+
+
+ Indicates whether to enable log file(s) to be deleted.
+
+
+
+
+ File attributes (Windows only).
+
+
+
+
+ Indicates whether to delete old log file on startup.
+
+
+
+
+ Indicates whether to replace file contents on each write instead of appending log message at the end.
+
+
+
+
+ Indicates whether concurrent writes to the log file by multiple processes on the same host.
+
+
+
+
+ Delay in milliseconds to wait before attempting to write to the file again.
+
+
+
+
+ Maximum number of log filenames that should be stored as existing.
+
+
+
+
+ Indicates whether concurrent writes to the log file by multiple processes on different network hosts.
+
+
+
+
+ Number of files to be kept open. Setting this to a higher value may improve performance in a situation where a single File target is writing to many files (such as splitting by level or by logger).
+
+
+
+
+ Maximum number of seconds that files are kept open. If this number is negative the files are not automatically closed after a period of inactivity.
+
+
+
+
+ Log file buffer size in bytes.
+
+
+
+
+ Indicates whether to automatically flush the file buffers after each log message.
+
+
+
+
+ Number of times the write is appended on the file before NLog discards the log message.
+
+
+
+
+ Indicates whether to keep log file open instead of opening and closing it on each logging event.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Condition expression. Log events who meet this condition will be forwarded to the wrapped target.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Windows domain name to change context to.
+
+
+
+
+ Required impersonation level.
+
+
+
+
+ Type of the logon provider.
+
+
+
+
+ Logon Type.
+
+
+
+
+ User account password.
+
+
+
+
+ Indicates whether to revert to the credentials of the process instead of impersonating another user.
+
+
+
+
+ Username to change context to.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Endpoint address.
+
+
+
+
+ Name of the endpoint configuration in WCF configuration file.
+
+
+
+
+ Indicates whether to use a WCF service contract that is one way (fire and forget) or two way (request-reply)
+
+
+
+
+ Client ID.
+
+
+
+
+ Indicates whether to include per-event properties in the payload sent to the server.
+
+
+
+
+ Indicates whether to use binary message encoding.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Layout that should be use to calculate the value for the parameter.
+
+
+
+
+ Name of the parameter.
+
+
+
+
+ Type of the parameter.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Text to be rendered.
+
+
+
+
+ Header.
+
+
+
+
+ Footer.
+
+
+
+
+ Indicates whether to send message as HTML instead of plain text.
+
+
+
+
+ Encoding to be used for sending e-mail.
+
+
+
+
+ Indicates whether to add new lines between log entries.
+
+
+
+
+ CC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com).
+
+
+
+
+ Recipients' email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com).
+
+
+
+
+ BCC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com).
+
+
+
+
+ Mail message body (repeated for each log message send in one mail).
+
+
+
+
+ Mail subject.
+
+
+
+
+ Sender's email address (e.g. joe@domain.com).
+
+
+
+
+ Indicates whether NewLine characters in the body should be replaced with tags.
+
+
+
+
+ Priority used for sending mails.
+
+
+
+
+ Indicates the SMTP client timeout.
+
+
+
+
+ SMTP Server to be used for sending.
+
+
+
+
+ SMTP Authentication mode.
+
+
+
+
+ Username used to connect to SMTP server (used when SmtpAuthentication is set to "basic").
+
+
+
+
+ Password used to authenticate against SMTP server (used when SmtpAuthentication is set to "basic").
+
+
+
+
+ Indicates whether SSL (secure sockets layer) should be used when communicating with SMTP server.
+
+
+
+
+ Port number that SMTP Server is listening on.
+
+
+
+
+ Indicates whether the default Settings from System.Net.MailSettings should be used.
+
+
+
+
+ Folder where applications save mail messages to be processed by the local SMTP server.
+
+
+
+
+ Specifies how outgoing email messages will be handled.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Layout used to format log messages.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Layout used to format log messages.
+
+
+
+
+ Encoding to be used when writing text to the queue.
+
+
+
+
+ Indicates whether to use the XML format when serializing message. This will also disable creating queues.
+
+
+
+
+ Indicates whether to check if a queue exists before writing to it.
+
+
+
+
+ Indicates whether to create the queue if it doesn't exists.
+
+
+
+
+ Label to associate with each message.
+
+
+
+
+ Name of the queue to write to.
+
+
+
+
+ Indicates whether to use recoverable messages (with guaranteed delivery).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Class name.
+
+
+
+
+ Method name. The method must be public and static. Use the AssemblyQualifiedName , https://msdn.microsoft.com/en-us/library/system.type.assemblyqualifiedname(v=vs.110).aspx e.g.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Layout used to format log messages.
+
+
+
+
+ Encoding to be used.
+
+
+
+
+ Maximum message size in bytes.
+
+
+
+
+ Indicates whether to append newline at the end of log message.
+
+
+
+
+ Action that should be taken if the will be more connections than .
+
+
+
+
+ Action that should be taken if the message is larger than maxMessageSize.
+
+
+
+
+ Network address.
+
+
+
+
+ Size of the connection cache (number of connections which are kept alive).
+
+
+
+
+ Indicates whether to keep connection open whenever possible.
+
+
+
+
+ Maximum current connections. 0 = no maximum.
+
+
+
+
+ Maximum queue size.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Encoding to be used.
+
+
+
+
+ Instance of that is used to format log messages.
+
+
+
+
+ Maximum message size in bytes.
+
+
+
+
+ Indicates whether to append newline at the end of log message.
+
+
+
+
+ Action that should be taken if the will be more connections than .
+
+
+
+
+ Action that should be taken if the message is larger than maxMessageSize.
+
+
+
+
+ Indicates whether to keep connection open whenever possible.
+
+
+
+
+ Size of the connection cache (number of connections which are kept alive).
+
+
+
+
+ Maximum current connections. 0 = no maximum.
+
+
+
+
+ Network address.
+
+
+
+
+ Maximum queue size.
+
+
+
+
+ Indicates whether to include source info (file name and line number) in the information sent over the network.
+
+
+
+
+ NDC item separator.
+
+
+
+
+ Indicates whether to include stack contents.
+
+
+
+
+ Indicates whether to include call site (class and method name) in the information sent over the network.
+
+
+
+
+ AppInfo field. By default it's the friendly name of the current AppDomain.
+
+
+
+
+ Indicates whether to include NLog-specific extensions to log4j schema.
+
+
+
+
+ Indicates whether to include dictionary contents.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Layout used to format log messages.
+
+
+
+
+ Indicates whether to perform layout calculation.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Layout used to format log messages.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Indicates whether performance counter should be automatically created.
+
+
+
+
+ Name of the performance counter category.
+
+
+
+
+ Counter help text.
+
+
+
+
+ Name of the performance counter.
+
+
+
+
+ Performance counter type.
+
+
+
+
+ The value by which to increment the counter.
+
+
+
+
+ Performance counter instance name.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Default filter to be applied when no specific rule matches.
+
+
+
+
+
+
+
+
+
+
+
+
+ Condition to be tested.
+
+
+
+
+ Resulting filter to be applied when the condition matches.
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Number of times to repeat each log message.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Number of retries that should be attempted on the wrapped target in case of a failure.
+
+
+
+
+ Time to wait between retries in milliseconds.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Layout used to format log messages.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the target.
+
+
+
+
+ Should we include the BOM (Byte-order-mark) for UTF? Influences the property. This will only work for UTF-8.
+
+
+
+
+ Encoding.
+
+
+
+
+ Web service method name. Only used with Soap.
+
+
+
+
+ Web service namespace. Only used with Soap.
+
+
+
+
+ Protocol to be used when calling web service.
+
+
+
+
+ Web service URL.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Footer layout.
+
+
+
+
+ Header layout.
+
+
+
+
+ Body layout (can be repeated multiple times).
+
+
+
+
+ Custom column delimiter value (valid when ColumnDelimiter is set to 'Custom').
+
+
+
+
+ Column delimiter.
+
+
+
+
+ Quote Character.
+
+
+
+
+ Quoting mode.
+
+
+
+
+ Indicates whether CVS should include header.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Layout of the column.
+
+
+
+
+ Name of the column.
+
+
+
+
+
+
+
+
+
+
+
+
+ Option to suppress the extra spaces in the output json
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Determines wether or not this attribute will be Json encoded.
+
+
+
+
+ Layout that will be rendered as the attribute's value.
+
+
+
+
+ Name of the attribute.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Footer layout.
+
+
+
+
+ Header layout.
+
+
+
+
+ Body layout (can be repeated multiple times).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Layout text.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Action to be taken when filter matches.
+
+
+
+
+ Condition expression.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Action to be taken when filter matches.
+
+
+
+
+ Indicates whether to ignore case when comparing strings.
+
+
+
+
+ Layout to be used to filter log messages.
+
+
+
+
+ Substring to be matched.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Action to be taken when filter matches.
+
+
+
+
+ String to compare the layout to.
+
+
+
+
+ Indicates whether to ignore case when comparing strings.
+
+
+
+
+ Layout to be used to filter log messages.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Action to be taken when filter matches.
+
+
+
+
+ Indicates whether to ignore case when comparing strings.
+
+
+
+
+ Layout to be used to filter log messages.
+
+
+
+
+ Substring to be matched.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Action to be taken when filter matches.
+
+
+
+
+ String to compare the layout to.
+
+
+
+
+ Indicates whether to ignore case when comparing strings.
+
+
+
+
+ Layout to be used to filter log messages.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/PNP.Deployer.csproj b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/PNP.Deployer.csproj
new file mode 100644
index 000000000..d541991ee
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/PNP.Deployer.csproj
@@ -0,0 +1,206 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {C3C39474-E23C-4C7A-90C1-02153EC8E98A}
+ Exe
+ Properties
+ PNP.Deployer
+ PNP.Deployer
+ v4.5.2
+ 512
+ true
+ false
+ C:\Users\Administrator\Desktop\Test1234\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ 1
+ 1.0.0.%2a
+ false
+ true
+ true
+
+
+ x64
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ x64
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ false
+
+
+ 784DE199EBB09D44E7EC0A2F707636DB45441E8C
+
+
+ PNP.Deployer_TemporaryKey.pfx
+
+
+ true
+
+
+ false
+
+
+
+
+
+ true
+
+
+
+ ..\packages\CommandLineParser.1.9.71\lib\net45\CommandLine.dll
+ True
+
+
+ True
+
+
+
+
+
+ ..\packages\SharePointPnPCore2013.2.6.1608.0\lib\net45\Microsoft.Online.SharePoint.Client.Tenant.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+ ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll
+ True
+
+
+ ..\packages\NLog.4.3.6\lib\net45\NLog.dll
+ True
+
+
+ ..\packages\SharePointPnPCore2013.2.6.1608.0\lib\net45\OfficeDevPnP.Core.dll
+ True
+
+
+
+
+
+
+
+ ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll
+ True
+
+
+
+
+
+ ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Designer
+
+
+ Always
+ Designer
+
+
+ Designer
+
+
+ Designer
+
+
+ Designer
+
+
+
+
+
+
+ False
+ Microsoft .NET Framework 4.5.2 %28x86 and x64%29
+ true
+
+
+ False
+ .NET Framework 3.5 SP1
+ false
+
+
+
+
+ {0fe76181-46fe-451b-b7a0-83e8b455da5d}
+ PNP.Deployer.Common
+
+
+ {ca4e46f3-08f9-41e1-b90e-edcdad22ec22}
+ PNP.Deployer.ExtensibilityProviders.CSOM
+
+
+ {dc6cdda2-4db6-48a2-bbdb-9c05f84eb235}
+ PNP.Deployer.ExtensibilityProviders.SSOM
+
+
+
+
+ copy $(ProjectDir)Classes\XSD\Sequences.xsd $(ProjectDir)$(OutDir)
+copy $(ProjectDir)Classes\XSD\TokensConfiguration.xsd $(ProjectDir)$(OutDir)
+
+
+
\ No newline at end of file
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Program.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Program.cs
new file mode 100644
index 000000000..c1619004a
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Program.cs
@@ -0,0 +1,118 @@
+using NLog;
+using System;
+using System.IO;
+using System.Net;
+using System.Security;
+using System.Configuration;
+using Microsoft.SharePoint.Client;
+
+
+namespace PNP.Deployer
+{
+ class Program
+ {
+ #region Constants
+
+ private const string APP_SETTING_TOKENS_FILE = "clientTokensFile";
+ private const string LABEL_USERNAME = "Username";
+ private const string LABEL_PASSWORD = "Password";
+
+ #endregion
+
+
+ #region Private Members
+
+ private static Logger logger = LogManager.GetCurrentClassLogger();
+
+ #endregion
+
+
+ #region Private Methods
+
+ // ===========================================================================================================
+ ///
+ /// Returns the correct credential set based on the environment and PromptCredentials switch
+ ///
+ /// The environment on which the deployer is executed
+ /// Whether the user should be prompted for credentials or not (always true for online)
+ /// A NetworkCredential object
+ // ===========================================================================================================
+ private static ICredentials GetAuthenticationCredentials(EnvironmentType environment, bool prompt)
+ {
+ ICredentials credentials = CredentialCache.DefaultNetworkCredentials;
+
+ if(prompt || environment == EnvironmentType.Online)
+ {
+ string username = ConsoleUtility.GetInputAsText(LABEL_USERNAME);
+ SecureString password = ConsoleUtility.GetInputAsSecureString(LABEL_PASSWORD);
+ Console.WriteLine(string.Empty);
+
+ if(environment == EnvironmentType.Online)
+ {
+ credentials = new SharePointOnlineCredentials(username, password);
+ }
+ else
+ {
+ credentials = new NetworkCredential(username, password);
+ }
+ }
+
+ return credentials;
+ }
+
+ #endregion
+
+
+ #region Public Methods
+
+ public static void Main(string[] args)
+ {
+ try
+ {
+ // ============================================================
+ // Parses the arguments using the CommandLine Parsing Library
+ // ============================================================
+ DeployerOptions options = new DeployerOptions();
+
+ if (CommandLine.Parser.Default.ParseArguments(args, options))
+ {
+ // -------------------------------------------------
+ // Gets the credentials based on the arguments
+ // -------------------------------------------------
+ ICredentials credentials = GetAuthenticationCredentials(options.Environment, options.PromptCredentials);
+
+
+ // -------------------------------------------------
+ // Tokenizes the deployer's working directory
+ // -------------------------------------------------
+ logger.Section("Tokenizing working directory", LogLevel.Info);
+ Tokenizer tokenizer = new Tokenizer(Path.Combine(options.WorkingDirectory, ConfigurationManager.AppSettings[APP_SETTING_TOKENS_FILE]));
+ string tokenizedWorkingDirectory = tokenizer.TokenizeFolder(options.WorkingDirectory);
+
+
+ // -------------------------------------------------
+ // Initializes the deployer
+ // -------------------------------------------------
+ logger.Section("Initializing deployer", LogLevel.Info);
+ Deployer deployer = new Deployer(tokenizedWorkingDirectory, options.Environment, credentials);
+
+
+ // -------------------------------------------------
+ // Starts the deployment
+ // -------------------------------------------------
+ logger.Section("Launching deployer", LogLevel.Info);
+ deployer.Launch();
+
+ Environment.Exit((int)ExitCode.Success);
+ }
+ }
+ catch (Exception e)
+ {
+ logger.Error(e, e.Message);
+ Environment.Exit((int)ExitCode.Failure);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Properties/AssemblyInfo.cs b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..dd44fc21b
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("PNP.Deployer")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("PNP.Deployer")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("c3c39474-e23c-4c7a-90c1-02153ec8e98a")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/packages.config b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/packages.config
new file mode 100644
index 000000000..26d14e08a
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/PNP.Deployer/packages.config
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Samples/Provisioning.PnPDeployer.Console/README.md b/Samples/Provisioning.PnPDeployer.Console/README.md
new file mode 100644
index 000000000..a49267cfc
--- /dev/null
+++ b/Samples/Provisioning.PnPDeployer.Console/README.md
@@ -0,0 +1,221 @@
+# PNP.Deployer #
+
+### Summary ###
+`PNP.Deployer.exe` is a console application that makes it easy to deploy artifacts to SharePoint OnPremise/Online. Based on the [PnP Provisioning Engine](https://github.com/OfficeDev/PnP-Guidance/blob/551b9f6a66cf94058ba5497e310d519647afb20c/articles/Introducing-the-PnP-Provisioning-Engine.md), it wraps the engine's main functionnalities and provides a new layer responsible for handling [tokens](#tokens-accross-any-files), [authentication](#authentication-made-simple), [sequences](#sequences-for-a-configurable-deployment) and [logging](#easy-logging). Provide the `PnP templates`, define `sequences` in which you want the templates to be executed, specify whether you want to deploy everything `OnPrem` or `Online`, and your good to go.
+
+### Applies to ###
+- Office 365 Multi Tenant (MT)
+- Office 365 Dedicated (D)
+- SharePoint 2013 on-premises
+
+### Prerequisites ###
+The projects within the Visual Studio solution have a dependency on the `PnP Core library`. By default, they are all configured for using the [SharePoint PnP Core library for SharePoint 2013](#sequences-for-a-configurable-deployment) nugget package, but feel free to change the nugget package for the one that suits your needs :
+* [SharePoint PnP Core library for SharePoint 2013 (SharePointPnPCore2013)](https://www.nuget.org/packages/SharePointPnPCore2013)
+* [SharePoint PnP Core library for SharePoint 2016 (SharePointPnPCore2016)](https://www.nuget.org/packages/SharePointPnPCore2016)
+* [SharePoint PnP Core library for SharePoint 2013 (SharePointPnPCoreOnline)](https://www.nuget.org/packages/SharePointPnPCoreOnline)
+
+### Solution ###
+Solution | Author(s)
+---------|----------
+PNP.Deployer | Simon-Pierre Plante
+
+### Version history ###
+Version | Date | Comments
+---------| -----| --------
+1.0 | September 7th 2016 | Initial release
+
+### Disclaimer ###
+**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
+
+
+
+# How it works #
+
+Place the `PNP.Deployer` package in a location of your choice and configure the `PNP.Deployer.exe.config` file located in the `bin\release` folder.
+
+
+
+Provide a package with the following elements and call the deployer with the proper parameters:
+
+* The [artifact(s)](#files) that needs to be deployed (page layouts, images, css, js, etc)
+* The [template(s)](#templates) referencing those previous artifacts (XML templates based on the [PnP Schema](https://github.com/OfficeDev/PnP-Provisioning-Schema))
+* A [sequences](#sequences-file) file that tells the deployer the templates to apply in the desired order
+* Optionnaly, a [tokens](#tokens-file) file used for "tokenizing" the whole working directory before deploying
+
+
+
+
+# Features #
+
+### Command line parser for a better user experience ###
+The console application uses the [Command Line Parser Library](https://commandline.codeplex.com) in order to provide named arguments, a custom '--help' interface, and userfriendly command line argument exceptions.
+
+
+
+
+
+### Tokens accross any files ###
+The [PnP Provisioning Engine](https://github.com/OfficeDev/PnP-Guidance/blob/551b9f6a66cf94058ba5497e310d519647afb20c/articles/Introducing-the-PnP-Provisioning-Engine.md) already supports tokens within template files, but what if you need tokens accross static files such as `CSS` files or a simple `Page Layout`? The deployer uses a `Tokenizer` that copies the whole working directory and generates a `tokenized` version of it (MyDirectory_Tokenized), which becomes the final working directory used by the deployer. The fact that tokens can be used accross the whole working directory makes the `Tokenizer` really powerfull, allowing the user to use tokens in any static files that aren't necessarily loaded in memory by the deployer.
+
+```xml
+
+
+
+
+
+
+```
+
+```css
+.item {
+ background-image: url('{{HubUrl}}/SiteAssets/Images/item-background.png');
+}
+```
+
+### Authentication made simple ###
+No need to handle the different types of authentication methods for `on-premise` and `online` environments, the deployer will automatically use the current user's credentials or prompt for a specific user's credential based on the specified `Environment` and `PromptCredentials` parameters.
+
+### Sequences for a configurable deployment ###
+`Sequences` makes it easy to orchestrate the different templates and their firing order. The `xml` syntax allows the user to easily `ignore` a specific sequence or a specific template within a sequence.
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Easy logging ###
+The deployer uses [NLog](http://nlog-project.org/) for logging, which provides an easy way to configure the different output sources and their properties to the user's liking.
+
+
+
+Customize the different output sources and the overall behavior of the logging engine simply by altering the provided `NLog.config` file, following the [NLog configuration file documentation](https://github.com/NLog/NLog/wiki/Configuration-file).
+
+
+
+### Supports ".pnp" packages ###
+While supporting regular `.xml` templates, the deployer also supports the new `.pnp` open xml format. Specify a `.pnp` package just like a standard template within the [sequences.xml](#sequences-for-a-configurable-deployment) file and everything within the `.pnp` package will be deployed.
+
+```xml
+...
+
+
+
+
+...
+```
+
+
+# Project Structure #
+
+
+
+
+
+# Getting Started #
+
+### 1 - Configuring the deployer ###
+The configuration of the deployer is stored within the `PNP.Deployer.exe.config` file located in the `bin\release` folder. The \ section of the configuration file stores 3 kinds of information :
+* `clientSequencesFile` :
+ - The name of the sequences file that the deployer needs to look for (relative to the `WorkingDirectory` specified by the caller)
+* `clientTokensFile`
+ - The name of the tokens file that the deployer needs to look for (relative to the `WorkingDirectory` specified by the caller)
+* `token-*`
+ - The default tokens will be available for any package deployed by the deployer, and can be added to the \ section by adding entries with keys that are prefixed with `token-` followed by the name of the token that will become available within the client packages
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+### 2 - Configuring the client package ###
+
+#### Files ####
+The files can be organized as the user whishes, with as much folders as needed, as long as they are properly referenced by the `templates`. File references within `templates` are always relative to the `WorkingDirectory` specified while calling the deployer.
+
+#### Templates ####
+The templates can be organized as the user whishes, with as much folders as needed, as long as they are properly referenced by the `sequences` file. Template references within `sequences` are always relative to the `WorkingDirectory` specified while calling the deployer.
+
+#### Sequences file ####
+The `sequences` file must reflect the deployer's configuration by having the same name and being at the same location, which is once again relative to the `WorkingDirectory` specified while calling the deployer.
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### Tokens file ####
+While being optional, in order for the `tokens` file to be recognised, it also needs to reflect the deployer's configuration by having the same name and being at the same location, which is once again relative to the `WorkingDirectory` specified while calling the deployer.
+
+```xml
+
+
+
+
+
+
+
+```
+
+
+### 3 - Calling the deployer ###
+Once the deployer is in place and the client package is ready, simply call the deployer using the following syntax :
+
+```powershell
+.\[...]\bin\release\PNP.Deployer.exe --WorkingDirectory "[...]\MyPackage" --Environment "OnPrem|Online" [--PromptCredentials]
+```
+
+ * --WorkingDirectory
+ - (Required) The full path of the package that needs to be deployed
+ * --Environment
+ - (Required) Whether the deployment occurs on a "OnPrem" or "Online" environment
+ * --PromptCredentials
+ - (Optionnal) Prompt for credentials to deploy under a specific account (Always prompts when "Online")
+
+Another option is to use the benefits of an environment variable in order to avoid having to specify the absolute path to the deployer's exe, allowing the user to use this shorter alternative :
+
+```powershell
+PNP.Deployer.exe --WorkingDirectory "[...]\MyPackage" --Environment "OnPrem|Online" [--PromptCredentials]
+```