diff --git a/CHANGELOG.md b/CHANGELOG.md index b3b635561..211bcf726 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,26 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). ## [3.2.1810.0] Unreleased ### Added +- Add-PnPProvisioningSequence : Adds an in-memory sequence to an in-memory provisioning hierarchy +- Add-PnPProvisioningSite : Adds an in-memory site definition to a in-memory sequence +- Add-PnPProvisioningSubSite : Adds an in-memory sub site defintion to an in-memory site +- Apply-PnPProvisioningHierarchy : Applies a provisioninghierarchy with a site sequence to a tenant +- Get-PnPProvisioningSite : Returns a site as an in-memory object from a given provisioning hierarchy +- New-PnPProvisioningHierarchy : Creates a new in-memory provisioning hierarchy +- New-PnPProvisioningSequence : Creates a new in-memory provisioning sequence +- New-PnPProvisioningCommunicationSite : Creates a new in-memory communication site definition +- New-PnPProvisioningTeamNoGroupSite : Creates a new in-memory team site definition which has no associated office365 group +- New-PnPProvisioningTeamNoGroupSubSite : Creates a new in-memory team sub site definition which has no associated office365 group +- New-PnPProvisioningTeamSite : Creates a new in-memory team site definition +- Read-PnPProvisioningHierarchy : Reads an existing (file based) provisioning hierarchy into an in-memory instance +- Save-PnPProvisioningHierarchy : Saves an in-memory provisioning hierarchy to a pnp file +- Test-PnPProvisioningHierarchy : Tests an in-memory hierarchy if all template references are correct in the site sequence +- Get-PnPException : Returns the last occured exception that occured while using PowerShell. ### Changed +- Updated Set-PnPSite to allow for setting of a logo on modern team site +- Updated Get-PnPTerm to allow for -IncludeChildTerms parameter, which will load, if available all child terms +- Updated Get-PnPTerm to allow for only specifying the id of a termset, without needing to require to specify the termset and termgroup. ### Deprecated diff --git a/Commands/Admin/GetHubSite.cs b/Commands/Admin/GetHubSite.cs index 45b103d45..edf2b32c8 100644 --- a/Commands/Admin/GetHubSite.cs +++ b/Commands/Admin/GetHubSite.cs @@ -13,8 +13,8 @@ namespace SharePointPnP.PowerShell.Commands.Admin [CmdletHelp(@"Retrieve all or a specific hubsite.", Category = CmdletHelpCategory.TenantAdmin, SupportedPlatform = CmdletSupportedPlatform.Online)] - [CmdletExample(Code = @"PS:> Get-PnPStorageEntity", Remarks = "Returns all site storage entities/farm properties", SortOrder = 1)] - [CmdletExample(Code = @"PS:> Get-PnPTenantSite -Key MyKey", Remarks = "Returns the storage entity/farm property with the given key.", SortOrder = 2)] + [CmdletExample(Code = @"PS:> Get-PnPHubSite", Remarks = "Returns all hubsite properties", SortOrder = 1)] + [CmdletExample(Code = @"PS:> Get-PnPHubSite -Identity https://contoso.sharepoint.com/sites/myhubsite", Remarks = "Returns the properties of the specified hubsite", SortOrder = 2)] public class GetHubSite : PnPAdminCmdlet { [Parameter(Position = 0, ValueFromPipeline = true)] diff --git a/Commands/Base/GetException.cs b/Commands/Base/GetException.cs new file mode 100644 index 000000000..157a814b7 --- /dev/null +++ b/Commands/Base/GetException.cs @@ -0,0 +1,48 @@ +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Model; +using System.Collections; +using System.Collections.Generic; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Base +{ + [Cmdlet(VerbsCommon.Get, "PnPException")] + [CmdletHelp("Returns the last exception that occured", + @"Returns the last exception which can be used while debugging PnP Cmdlets", + Category = CmdletHelpCategory.Base)] + [CmdletExample( + Code = @"PS:> Get-PnPException", + Remarks = "Returns the last exception", + SortOrder = 1)] + [CmdletExample( + Code = @"PS:> Get-PnPException -All", + Remarks = "Returns all exceptions that occurred", + SortOrder = 2)] + public class GetException : PSCmdlet + { + [Parameter(Mandatory = false, HelpMessage = "Show all exceptions")] + public SwitchParameter All; + + protected override void ProcessRecord() + { + var exceptions = (ArrayList)this.SessionState.PSVariable.Get("error").Value; + if (exceptions.Count > 0) + { + var output = new List(); + if (All.IsPresent) + { + foreach (ErrorRecord exception in exceptions) + { + output.Add(new PnPException() { Message = exception.Exception.Message, Stacktrace = exception.Exception.StackTrace, ScriptLineNumber = exception.InvocationInfo.ScriptLineNumber, InvocationInfo = exception.InvocationInfo }); + } + } + else + { + var exception = (ErrorRecord)exceptions[0]; + output.Add(new PnPException() { Message = exception.Exception.Message, Stacktrace = exception.Exception.StackTrace, ScriptLineNumber = exception.InvocationInfo.ScriptLineNumber, InvocationInfo = exception.InvocationInfo }); + } + WriteObject(output, true); + } + } + } +} diff --git a/Commands/Base/PipeBinds/ProvisioningSequencePipebind.cs b/Commands/Base/PipeBinds/ProvisioningSequencePipebind.cs new file mode 100644 index 000000000..0e9bb5d75 --- /dev/null +++ b/Commands/Base/PipeBinds/ProvisioningSequencePipebind.cs @@ -0,0 +1,36 @@ +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using System; +using System.Linq; + +namespace SharePointPnP.PowerShell.Commands.Base.PipeBinds +{ + public sealed class ProvisioningSequencePipeBind + { + private readonly ProvisioningSequence _sequence; + private readonly string _identity; + + public ProvisioningSequencePipeBind(ProvisioningSequence sequence) + { + _sequence = sequence; + } + + public ProvisioningSequencePipeBind(string identity) + { + _identity = identity; + } + + public ProvisioningSequence GetSequenceFromHierarchy(ProvisioningHierarchy hierarchy) + { + var id = string.Empty; + if(_sequence == null) + { + id = _identity; + } else + { + id = _sequence.ID; + } + return hierarchy.Sequences.FirstOrDefault(s => s.ID == id); + } + + } +} diff --git a/Commands/Base/PipeBinds/ProvisioningSitePipeBind.cs b/Commands/Base/PipeBinds/ProvisioningSitePipeBind.cs new file mode 100644 index 000000000..d986315ee --- /dev/null +++ b/Commands/Base/PipeBinds/ProvisioningSitePipeBind.cs @@ -0,0 +1,35 @@ +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using System; +using System.Linq; + +namespace SharePointPnP.PowerShell.Commands.Base.PipeBinds +{ + public sealed class ProvisioningSitePipeBind + { + private readonly SiteCollection _site; + + public ProvisioningSitePipeBind(TeamSiteCollection site) + { + _site = site; + } + + public ProvisioningSitePipeBind(TeamNoGroupSiteCollection site) + { + _site = site; + } + + public ProvisioningSitePipeBind(CommunicationSiteCollection site) + { + _site = site; + } + + public SiteCollection Site => _site; + + public SiteCollection GetSiteFromSequence(ProvisioningSequence sequence) + { + return sequence.SiteCollections.FirstOrDefault(s => s.Id == _site.Id); + } + } +} + + diff --git a/Commands/Model/PnPException.cs b/Commands/Model/PnPException.cs new file mode 100644 index 000000000..4573b1e9c --- /dev/null +++ b/Commands/Model/PnPException.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Text; +using System.Threading.Tasks; + +namespace SharePointPnP.PowerShell.Commands.Model +{ + public class PnPException + { + public string Message; + public string Stacktrace; + public int ScriptLineNumber; + public InvocationInfo InvocationInfo; + } +} diff --git a/Commands/ModuleFiles/SharePointPnP.PowerShell.2013.Commands.Format.ps1xml b/Commands/ModuleFiles/SharePointPnP.PowerShell.2013.Commands.Format.ps1xml index 2e25fa199..1994af1e7 100644 --- a/Commands/ModuleFiles/SharePointPnP.PowerShell.2013.Commands.Format.ps1xml +++ b/Commands/ModuleFiles/SharePointPnP.PowerShell.2013.Commands.Format.ps1xml @@ -795,6 +795,9 @@ 36 left + + + @@ -805,6 +808,16 @@ Id + + + if($_.IsObjectPropertyInstantiated("Terms")){ + $_.TermsCount + } + else{ + "$($_.TermsCount) (Not loaded)" + } + + @@ -1270,5 +1283,51 @@ + + TaxonomySession + + Microsoft.SharePoint.Client.Taxonomy.TaxonomySession + + + + + + left + + + + + + + TermStores + + + + + + + + PnPException + + SharePointPnP.PowerShell.Commands.Model.PnPException + + + + + + + Message + + + Stacktrace + + + ScriptLineNumber + + + + + + \ No newline at end of file diff --git a/Commands/ModuleFiles/SharePointPnP.PowerShell.2016.Commands.Format.ps1xml b/Commands/ModuleFiles/SharePointPnP.PowerShell.2016.Commands.Format.ps1xml index 2e25fa199..1994af1e7 100644 --- a/Commands/ModuleFiles/SharePointPnP.PowerShell.2016.Commands.Format.ps1xml +++ b/Commands/ModuleFiles/SharePointPnP.PowerShell.2016.Commands.Format.ps1xml @@ -795,6 +795,9 @@ 36 left + + + @@ -805,6 +808,16 @@ Id + + + if($_.IsObjectPropertyInstantiated("Terms")){ + $_.TermsCount + } + else{ + "$($_.TermsCount) (Not loaded)" + } + + @@ -1270,5 +1283,51 @@ + + TaxonomySession + + Microsoft.SharePoint.Client.Taxonomy.TaxonomySession + + + + + + left + + + + + + + TermStores + + + + + + + + PnPException + + SharePointPnP.PowerShell.Commands.Model.PnPException + + + + + + + Message + + + Stacktrace + + + ScriptLineNumber + + + + + + \ No newline at end of file diff --git a/Commands/ModuleFiles/SharePointPnP.PowerShell.Core.Format.ps1xml b/Commands/ModuleFiles/SharePointPnP.PowerShell.Core.Format.ps1xml index b5da6dcc1..49ef10b99 100644 --- a/Commands/ModuleFiles/SharePointPnP.PowerShell.Core.Format.ps1xml +++ b/Commands/ModuleFiles/SharePointPnP.PowerShell.Core.Format.ps1xml @@ -589,30 +589,30 @@ if($_.Name -eq ""){ - $_.ServerRelativeUrl.TrimEnd("/").Substring($_.ServerRelativeUrl.TrimEnd("/").LastIndexOf("/") + 1) + $_.ServerRelativeUrl.TrimEnd("/").Substring($_.ServerRelativeUrl.TrimEnd("/").LastIndexOf("/") + 1) } else{ - $_.Name + $_.Name } if($_.GetType().Name -eq "Folder" -and $_.Name -eq ""){ - "Subweb" + "Subweb" } else{ - $_.GetType().Name + $_.GetType().Name } if($_.GetType().Name -eq "File"){ - $_.Length + $_.Length } else{ - $_.ItemCount + $_.ItemCount } @@ -801,6 +801,9 @@ 36 left + + + @@ -811,6 +814,16 @@ Id + + + if($_.IsObjectPropertyInstantiated("Terms")){ + $_.TermsCount + } + else{ + "$($_.TermsCount) (Not loaded)" + } + + @@ -1290,7 +1303,7 @@ left - + @@ -1366,7 +1379,8 @@ - + + TenantSiteDesign Microsoft.Online.SharePoint.TenantAdministration.TenantSiteDesign @@ -1439,7 +1453,7 @@ Version - + Content @@ -1515,7 +1529,7 @@ left - + left @@ -1541,7 +1555,7 @@ Order - + PropertiesJson @@ -1549,12 +1563,12 @@ - + NavigationNode Microsoft.SharePoint.Client.NavigationNode - + @@ -1780,5 +1794,28 @@ + + PnPException + + SharePointPnP.PowerShell.Commands.Model.PnPException + + + + + + + Message + + + Stacktrace + + + ScriptLineNumber + + + + + + \ No newline at end of file diff --git a/Commands/ModuleFiles/SharePointPnP.PowerShell.Online.Commands.Format.ps1xml b/Commands/ModuleFiles/SharePointPnP.PowerShell.Online.Commands.Format.ps1xml index c90625aab..22675cb17 100644 --- a/Commands/ModuleFiles/SharePointPnP.PowerShell.Online.Commands.Format.ps1xml +++ b/Commands/ModuleFiles/SharePointPnP.PowerShell.Online.Commands.Format.ps1xml @@ -583,30 +583,30 @@ if($_.Name -eq ""){ - $_.ServerRelativeUrl.TrimEnd("/").Substring($_.ServerRelativeUrl.TrimEnd("/").LastIndexof("/") + 1) + $_.ServerRelativeUrl.TrimEnd("/").Substring($_.ServerRelativeUrl.TrimEnd("/").LastIndexof("/") + 1) } else{ - $_.Name + $_.Name } if($_.GetType().Name -eq "Folder" -and $_.Name -eq ""){ - "Subweb" + "Subweb" } else{ - $_.GetType().Name + $_.GetType().Name } if($_.GetType().Name -eq "File"){ - $_.Length + $_.Length } else{ - $_.ItemCount + $_.ItemCount } @@ -795,6 +795,9 @@ 36 left + + + @@ -805,6 +808,16 @@ Id + + + if($_.IsObjectPropertyInstantiated("Terms")){ + $_.TermsCount + } + else{ + "$($_.TermsCount) (Not loaded)" + } + + @@ -1685,7 +1698,7 @@ - + SPOWebAppServicePrincipalPermissionRequest Microsoft.Online.SharePoint.TenantAdministration.Internal.SPOWebAppServicePrincipalPermissionRequest @@ -1766,5 +1779,95 @@ + + TaxonomySession + + Microsoft.SharePoint.Client.Taxonomy.TaxonomySession + + + + + + left + + + + + + + TermStores + + + + + + + + SiteCollection + + OfficeDevPnP.Core.Framework.Provisioning.Model.SiteCollection + + + + + + left + + + + left + + + + left + + + + left + + + + + + + Id + + + Title + + + Templates + + + Sites + + + + + + + + PnPException + + SharePointPnP.PowerShell.Commands.Model.PnPException + + + + + + + Message + + + Stacktrace + + + ScriptLineNumber + + + + + + - \ No newline at end of file + diff --git a/Commands/Properties/AssemblyInfo.cs b/Commands/Properties/AssemblyInfo.cs index 97eed533c..727af4580 100644 --- a/Commands/Properties/AssemblyInfo.cs +++ b/Commands/Properties/AssemblyInfo.cs @@ -44,6 +44,6 @@ // 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("3.1.1809.0")] -[assembly: AssemblyFileVersion("3.1.1809.0")] +[assembly: AssemblyVersion("3.2.1810.0")] +[assembly: AssemblyFileVersion("3.2.1810.0")] [assembly: InternalsVisibleTo("SharePointPnP.PowerShell.Tests")] \ No newline at end of file diff --git a/Commands/Provisioning/AddProvisioningSequence.cs b/Commands/Provisioning/AddProvisioningSequence.cs new file mode 100644 index 000000000..2ed6fabdf --- /dev/null +++ b/Commands/Provisioning/AddProvisioningSequence.cs @@ -0,0 +1,43 @@ +#if !ONPREMISES +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using System; +using System.Linq; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Provisioning +{ + [Cmdlet(VerbsCommon.Add, "PnPProvisioningSequence", SupportsShouldProcess = true)] + [CmdletHelp("Adds a provisioning sequence object to a provisioning hierarchy", + Category = CmdletHelpCategory.Provisioning, SupportedPlatform = CmdletSupportedPlatform.Online)] + [CmdletExample( + Code = @"PS:> Add-PnPProvisioningSequence -Hierarchy $myhierarchy -Sequence $mysequence", + Remarks = "Adds an existing sequence object to an existing hierarchy object", + SortOrder = 1)] + [CmdletExample( + Code = @"PS:> New-PnPProvisioningSequence -Id ""MySequence"" | Add-PnPProvisioningSequence -Hierarchy $hierarchy", + Remarks = "Creates a new instance of a provisioning sequence object and sets the Id to the value specified, then the sequence is added to an existing hierarchy object", + SortOrder = 2)] + public class AddProvisioningSequence : PSCmdlet + { + [Parameter(Mandatory = true, HelpMessage = "The hierarchy to add the sequence to", ParameterSetName = ParameterAttribute.AllParameterSets)] + public ProvisioningHierarchy Hierarchy; + + [Parameter(Mandatory = true, HelpMessage = "Optional Id of the sequence", ParameterSetName = ParameterAttribute.AllParameterSets, ValueFromPipeline = true)] + public ProvisioningSequence Sequence; + + protected override void ProcessRecord() + { + if (Hierarchy.Sequences.FirstOrDefault(s => s.ID == Sequence.ID) == null) + { + Hierarchy.Sequences.Add(Sequence); + WriteObject(Hierarchy); + } + else + { + WriteError(new ErrorRecord(new Exception($"Sequence with ID {Sequence.ID} already exists in hierarchy"), "DUPLICATESEQUENCEID", ErrorCategory.InvalidData, Sequence)); + } + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Provisioning/AddProvisioningSite.cs b/Commands/Provisioning/AddProvisioningSite.cs new file mode 100644 index 000000000..e98888827 --- /dev/null +++ b/Commands/Provisioning/AddProvisioningSite.cs @@ -0,0 +1,31 @@ +#if !ONPREMISES +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Provisioning +{ + [Cmdlet(VerbsCommon.Add, "PnPProvisioningSite", SupportsShouldProcess = true)] + [CmdletHelp("Adds a provisioning sequence object to a provisioning hierarchy", + Category = CmdletHelpCategory.Provisioning, SupportedPlatform = CmdletSupportedPlatform.Online)] + [CmdletExample( + Code = @"PS:> Add-PnPProvisioningSite -Site $myteamsite -Sequence $mysequence", + Remarks = "Adds an existing site object to an existing hierarchy sequence", + SortOrder = 1)] + public class AddProvisioningSite : PSCmdlet + { + [Parameter(Mandatory = true, ValueFromPipeline = true)] + public ProvisioningSitePipeBind Site; + + [Parameter(Mandatory = true, HelpMessage = "The sequence to add the site to", ValueFromPipeline = true)] + public ProvisioningSequence Sequence; + + protected override void ProcessRecord() + { + Sequence.SiteCollections.Add(Site.Site); + WriteObject(Sequence); + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Provisioning/AddProvisioningSubSite.cs b/Commands/Provisioning/AddProvisioningSubSite.cs new file mode 100644 index 000000000..89669e8db --- /dev/null +++ b/Commands/Provisioning/AddProvisioningSubSite.cs @@ -0,0 +1,38 @@ +#if !ONPREMISES +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using System; +using System.Linq; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Provisioning +{ + [Cmdlet(VerbsCommon.Add, "PnPProvisioningSubSite", SupportsShouldProcess = true)] + [CmdletHelp("Adds a provisioning sequence object to a provisioning site object", + Category = CmdletHelpCategory.Provisioning, SupportedPlatform = CmdletSupportedPlatform.Online)] + [CmdletExample( + Code = @"PS:> Add-PnPProvisioningSubSite -Site $mysite -SubSite $mysubsite", + Remarks = "Adds an existing subsite object to an existing hierarchy sequence site object", + SortOrder = 1)] + public class AddProvisioningSubSite : PSCmdlet + { + [Parameter(Mandatory = true, HelpMessage = "The subsite to add")] + public TeamNoGroupSubSite SubSite; + + [Parameter(Mandatory = true, HelpMessage = "The site to add the subsite to", ValueFromPipeline = true)] + public SiteCollection Site; + + protected override void ProcessRecord() + { + if (Site.Sites.Cast().FirstOrDefault(s => s.Url == SubSite.Url) == null) + { + Site.Sites.Add(SubSite); + } + else + { + WriteError(new ErrorRecord(new Exception($"Site with URL {SubSite.Url} already exists in sequence"), "DUPLICATEURL", ErrorCategory.InvalidData, SubSite)); + } + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Provisioning/AddProvisioningTemplate.cs b/Commands/Provisioning/AddProvisioningTemplate.cs new file mode 100644 index 000000000..215ff1a1c --- /dev/null +++ b/Commands/Provisioning/AddProvisioningTemplate.cs @@ -0,0 +1,37 @@ +#if !ONPREMISES +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; +using System; +using System.Linq; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Provisioning +{ + [Cmdlet(VerbsCommon.Add, "PnPProvisioningTemplate", SupportsShouldProcess = true)] + [CmdletHelp("Adds a provisioning template object to a provisioning hierarchy", + Category = CmdletHelpCategory.Provisioning, SupportedPlatform = CmdletSupportedPlatform.Online)] + [CmdletExample( + Code = @"PS:> Add-PnPProvisioningTemplate -Hierarchy $myhierarchy -Template $mytemplate", + Remarks = "Adds an existing sequence object to an existing hierarchy object", + SortOrder = 1)] + public class AddProvisioningTemplate : PSCmdlet + { + [Parameter(Mandatory = true, HelpMessage = "The template to add to the hierarchy")] + public ProvisioningTemplate Template; + + [Parameter(Mandatory = true, HelpMessage = "The hierarchy to add the template to", ValueFromPipeline = true)] + public ProvisioningHierarchy Hierarchy; + + protected override void ProcessRecord() + { + if(Hierarchy.Templates.FirstOrDefault(t => t.Id == Template.Id) == null) + { + Hierarchy.Templates.Add(Template); + } else { + WriteError(new ErrorRecord(new Exception($"Template with ID {Template.Id} already exists in hierarchy"), "DUPLICATETEMPLATE", ErrorCategory.InvalidData, Template)); + } + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Provisioning/ApplyProvisioningHierarchy.cs b/Commands/Provisioning/ApplyProvisioningHierarchy.cs new file mode 100644 index 000000000..41be05e31 --- /dev/null +++ b/Commands/Provisioning/ApplyProvisioningHierarchy.cs @@ -0,0 +1,266 @@ +#if !ONPREMISES +using Microsoft.SharePoint.Client; +using OfficeDevPnP.Core.Framework.Provisioning.Connectors; +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using OfficeDevPnP.Core.Framework.Provisioning.ObjectHandlers; +using OfficeDevPnP.Core.Framework.Provisioning.Providers; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Provisioning +{ + [Cmdlet("Apply", "PnPProvisioningHierarchy", SupportsShouldProcess = true)] + [CmdletHelp("Adds a provisioning sequence object to a provisioning site object", + Category = CmdletHelpCategory.Provisioning, SupportedPlatform = CmdletSupportedPlatform.Online)] + [CmdletExample( + Code = @"PS:> Apply-PnPProvisioningHierarchy -Path myfile.pnp", + Remarks = "Will read the provisioning hierarchy from the filesystem and will apply the sequences in the hierarchy", + SortOrder = 1)] + [CmdletExample( + Code = @"PS:> Apply-PnPProvisioningHierarchy -Path myfile.pnp -SequenceId ""mysequence""", + Remarks = "Will read the provisioning hierarchy from the filesystem and will apply the specified sequence in the hierarchy", + SortOrder = 1)] + [CmdletExample( + Code = @"PS:> Apply-PnPProvisioningHierarchy -Path myfile.pnp -Parameters @{""ListTitle""=""Projects"";""parameter2""=""a second value""}", + Remarks = @"Applies a provisioning hierarchy template to the current tenant. It will populate the parameter in the template the values as specified and in the template you can refer to those values with the {parameter:} token. + +For instance with the example above, specifying {parameter:ListTitle} in your template will translate to 'Projects' when applying the template. These tokens can be used in most string values in a template.", + SortOrder = 3)] + public class ApplyProvisioningHierarchy : PnPAdminCmdlet + { + private const string ParameterSet_PATH = "By Path"; + private const string ParameterSet_OBJECT = "By Object"; + + private ProgressRecord progressRecord = new ProgressRecord(0, "Activity", "Status"); + private ProgressRecord subProgressRecord = new ProgressRecord(1, "Activity", "Status"); + + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, ValueFromPipeline = true, HelpMessage = "Path to the xml or pnp file containing the provisioning hierarchy.", ParameterSetName = ParameterSet_PATH)] + public string Path; + + [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ParameterSetName = ParameterSet_OBJECT)] + public ProvisioningHierarchy Hierarchy; + + [Parameter(Mandatory = false)] + public string SequenceId; + + [Parameter(Mandatory = false, HelpMessage = "Root folder where resources/files that are being referenced in the template are located. If not specified the same folder as where the provisioning template is located will be used.", ParameterSetName = ParameterAttribute.AllParameterSets)] + public string ResourceFolder; + + [Parameter(Mandatory = false, HelpMessage = "Allows you to only process a specific part of the template. Notice that this might fail, as some of the handlers require other artifacts in place if they are not part of what your applying.", ParameterSetName = ParameterAttribute.AllParameterSets)] + public Handlers Handlers; + + [Parameter(Mandatory = false, HelpMessage = "Allows you to run all handlers, excluding the ones specified.", ParameterSetName = ParameterAttribute.AllParameterSets)] + public Handlers ExcludeHandlers; + + [Parameter(Mandatory = false, HelpMessage = "Allows you to specify ExtensbilityHandlers to execute while applying a template", ParameterSetName = ParameterAttribute.AllParameterSets)] + public ExtensibilityHandler[] ExtensibilityHandlers; + + [Parameter(Mandatory = false, HelpMessage = "Allows you to specify ITemplateProviderExtension to execute while applying a template.", ParameterSetName = ParameterAttribute.AllParameterSets)] + public ITemplateProviderExtension[] TemplateProviderExtensions; + + [Parameter(Mandatory = false, HelpMessage = "Allows you to specify parameters that can be referred to in the hierarchy by means of the {parameter:} token. See examples on how to use this parameter.", ParameterSetName = ParameterAttribute.AllParameterSets)] + public Hashtable Parameters; + + [Parameter(Mandatory = false, HelpMessage = "Specify this parameter if you want to overwrite and/or create properties that are known to be system entries (starting with vti_, dlc_, etc.)", ParameterSetName = ParameterAttribute.AllParameterSets)] + public SwitchParameter OverwriteSystemPropertyBagValues; + + [Parameter(Mandatory = false, HelpMessage = "Ignore duplicate data row errors when the data row in the template already exists.", ParameterSetName = ParameterAttribute.AllParameterSets)] + public SwitchParameter IgnoreDuplicateDataRowErrors; + + [Parameter(Mandatory = false, HelpMessage = "If set content types will be provisioned if the target web is a subweb.")] + public SwitchParameter ProvisionContentTypesToSubWebs; + + [Parameter(Mandatory = false, HelpMessage = "If set fields will be provisioned if the target web is a subweb.")] + public SwitchParameter ProvisionFieldsToSubWebs; + + [Parameter(Mandatory = false, HelpMessage = "Override the RemoveExistingNodes attribute in the Navigation elements of the template. If you specify this value the navigation nodes will always be removed before adding the nodes in the template")] + public SwitchParameter ClearNavigation; + + protected override void ExecuteCmdlet() + { + var applyingInformation = new ProvisioningTemplateApplyingInformation(); + + if (MyInvocation.BoundParameters.ContainsKey("Handlers")) + { + applyingInformation.HandlersToProcess = Handlers; + } + if (MyInvocation.BoundParameters.ContainsKey("ExcludeHandlers")) + { + foreach (var handler in (Handlers[])Enum.GetValues(typeof(Handlers))) + { + if (!ExcludeHandlers.Has(handler) && handler != Handlers.All) + { + Handlers = Handlers | handler; + } + } + applyingInformation.HandlersToProcess = Handlers; + } + + if (ExtensibilityHandlers != null) + { + applyingInformation.ExtensibilityHandlers = ExtensibilityHandlers.ToList(); + } + + applyingInformation.ProgressDelegate = (message, step, total) => + { + if (message != null) + { + var percentage = Convert.ToInt32((100 / Convert.ToDouble(total)) * Convert.ToDouble(step)); + progressRecord.Activity = $"Applying template to tenant"; + progressRecord.StatusDescription = message; + progressRecord.PercentComplete = percentage; + progressRecord.RecordType = ProgressRecordType.Processing; + WriteProgress(progressRecord); + } + }; + + var warningsShown = new List(); + + applyingInformation.MessagesDelegate = (message, type) => + { + switch (type) + { + case ProvisioningMessageType.Warning: + { + if (!warningsShown.Contains(message)) + { + WriteWarning(message); + warningsShown.Add(message); + } + break; + } + case ProvisioningMessageType.Progress: + { + if (message != null) + { + var activity = message; + if (message.IndexOf("|") > -1) + { + var messageSplitted = message.Split('|'); + if (messageSplitted.Length == 4) + { + var current = double.Parse(messageSplitted[2]); + var total = double.Parse(messageSplitted[3]); + subProgressRecord.RecordType = ProgressRecordType.Processing; + subProgressRecord.Activity = string.IsNullOrEmpty(messageSplitted[0]) ? "-" : messageSplitted[0]; + subProgressRecord.StatusDescription = string.IsNullOrEmpty(messageSplitted[1]) ? "-" : messageSplitted[1]; + subProgressRecord.PercentComplete = Convert.ToInt32((100 / total) * current); + WriteProgress(subProgressRecord); + } + else + { + subProgressRecord.Activity = "Processing"; + subProgressRecord.RecordType = ProgressRecordType.Processing; + subProgressRecord.StatusDescription = activity; + subProgressRecord.PercentComplete = 0; + WriteProgress(subProgressRecord); + } + } + else + { + subProgressRecord.Activity = "Processing"; + subProgressRecord.RecordType = ProgressRecordType.Processing; + subProgressRecord.StatusDescription = activity; + subProgressRecord.PercentComplete = 0; + WriteProgress(subProgressRecord); + } + } + break; + } + case ProvisioningMessageType.Completed: + { + + WriteProgress(new ProgressRecord(1, message, " ") { RecordType = ProgressRecordType.Completed }); + break; + } + } + }; + + applyingInformation.OverwriteSystemPropertyBagValues = OverwriteSystemPropertyBagValues; + applyingInformation.IgnoreDuplicateDataRowErrors = IgnoreDuplicateDataRowErrors; + applyingInformation.ClearNavigation = ClearNavigation; + applyingInformation.ProvisionContentTypesToSubWebs = ProvisionContentTypesToSubWebs; + applyingInformation.ProvisionFieldsToSubWebs = ProvisionFieldsToSubWebs; + + ProvisioningHierarchy hierarchyToApply = null; + + switch (ParameterSetName) + { + case ParameterSet_PATH: + { + hierarchyToApply = GetHierarchy(); + break; + } + case ParameterSet_OBJECT: + { + hierarchyToApply = Hierarchy; + if (ResourceFolder != null) + { + var fileSystemConnector = new FileSystemConnector(ResourceFolder, ""); + hierarchyToApply.Connector = fileSystemConnector; + } + else + { + if (Path != null) + { + if (!System.IO.Path.IsPathRooted(Path)) + { + Path = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, Path); + } + } + else + { + Path = SessionState.Path.CurrentFileSystemLocation.Path; + } + var fileInfo = new FileInfo(Path); + var fileConnector = new FileSystemConnector(fileInfo.DirectoryName, ""); + hierarchyToApply.Connector = fileConnector; + } + break; + } + } + if (Parameters != null) + { + foreach (var parameter in Parameters.Keys) + { + if (hierarchyToApply.Parameters.ContainsKey(parameter.ToString())) + { + hierarchyToApply.Parameters[parameter.ToString()] = Parameters[parameter].ToString(); + } + else + { + hierarchyToApply.Parameters.Add(parameter.ToString(), Parameters[parameter].ToString()); + } + } + } + if (!string.IsNullOrEmpty(SequenceId)) + { + Tenant.ApplyProvisionHierarchy(hierarchyToApply, SequenceId, applyingInformation); + } + else + { + foreach (var sequence in hierarchyToApply.Sequences) + { + Tenant.ApplyProvisionHierarchy(hierarchyToApply, sequence.ID, applyingInformation); + } + } + + } + + private ProvisioningHierarchy GetHierarchy() + { + if(!System.IO.Path.IsPathRooted(Path)) + { + Path = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, Path); + } + return ReadProvisioningHierarchy.LoadProvisioningHierarchyFromFile(Path, TemplateProviderExtensions); + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Provisioning/ApplyProvisioningTemplate.cs b/Commands/Provisioning/ApplyProvisioningTemplate.cs index 15f068c61..a09a567d7 100644 --- a/Commands/Provisioning/ApplyProvisioningTemplate.cs +++ b/Commands/Provisioning/ApplyProvisioningTemplate.cs @@ -12,6 +12,7 @@ using OfficeDevPnP.Core.Framework.Provisioning.Providers; using SharePointPnP.PowerShell.Commands.Components; using System.Collections.Generic; +using SharePointPnP.PowerShell.Commands.Utilities; namespace SharePointPnP.PowerShell.Commands.Provisioning { @@ -152,7 +153,7 @@ protected override void ExecuteCmdlet() // If we don't have the -InputInstance parameter, we load the template from the source connector Stream stream = fileConnector.GetFileStream(templateFileName); - var isOpenOfficeFile = IsOpenOfficeFile(stream); + var isOpenOfficeFile = FileUtilities.IsOpenOfficeFile(stream); XMLTemplateProvider provider; if (isOpenOfficeFile) { @@ -355,23 +356,6 @@ protected override void ExecuteCmdlet() WriteProgress(new ProgressRecord(0, $"Applying template to {SelectedWeb.Url}", " ") { RecordType = ProgressRecordType.Completed }); } - internal static bool IsOpenOfficeFile(Stream stream) - { - bool istrue = false; - // SIG 50 4B 03 04 14 00 - - byte[] bytes = new byte[6]; - stream.Read(bytes, 0, 6); - var signature = string.Empty; - foreach (var b in bytes) - { - signature += b.ToString("X2"); - } - if (signature == "504B03041400") - { - istrue = true; - } - return istrue; - } + } } diff --git a/Commands/Provisioning/GetProvisioningSequence.cs b/Commands/Provisioning/GetProvisioningSequence.cs new file mode 100644 index 000000000..f59079421 --- /dev/null +++ b/Commands/Provisioning/GetProvisioningSequence.cs @@ -0,0 +1,39 @@ +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; +using System; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Provisioning +{ + [Cmdlet(VerbsCommon.Get, "PnPProvisioningSequence", SupportsShouldProcess = true)] + [CmdletHelp("Returns one ore more provisioning sequence object(s) from a provisioning hierarchy", + Category = CmdletHelpCategory.Provisioning)] + [CmdletExample( + Code = @"PS:> Get-PnPProvisioningSequence -Hierarchy $myhierarchy", + Remarks = "Returns all sequences from the specified hierarchy", + SortOrder = 1)] + [CmdletExample( + Code = @"PS:> Get-PnPProvisioningSequence -Hierarchy $myhierarchy -Identity ""mysequence""", + Remarks = "Returns the specified sequence from the specified hierarchy", + SortOrder = 2)] + public class GetProvisioningSequence : PSCmdlet + { + [Parameter(Mandatory = true, HelpMessage = "The hierarchy to retrieve the sequence from", ParameterSetName = ParameterAttribute.AllParameterSets)] + public ProvisioningHierarchy Hierarchy; + + [Parameter(Mandatory = false, HelpMessage = "Optional Id of the sequence", ParameterSetName = ParameterAttribute.AllParameterSets, ValueFromPipeline = true)] + public ProvisioningSequencePipeBind Identity; + protected override void ProcessRecord() + { + if (!MyInvocation.BoundParameters.ContainsKey("Identity")) + { + WriteObject(Hierarchy.Sequences, true); + } + else + { + WriteObject(Identity.GetSequenceFromHierarchy(Hierarchy)); + } + } + } +} diff --git a/Commands/Provisioning/GetProvisioningSite.cs b/Commands/Provisioning/GetProvisioningSite.cs new file mode 100644 index 000000000..d736ac23f --- /dev/null +++ b/Commands/Provisioning/GetProvisioningSite.cs @@ -0,0 +1,39 @@ +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; +using System; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Provisioning +{ + [Cmdlet(VerbsCommon.Get, "PnPProvisioningSite", SupportsShouldProcess = true)] + [CmdletHelp("Returns one ore more provisioning sequence object(s) from a provisioning hierarchy", + Category = CmdletHelpCategory.Provisioning)] + [CmdletExample( + Code = @"PS:> Get-PnPProvisioningSite -Sequence $mysequence", + Remarks = "Returns all sites from the specified sequence", + SortOrder = 1)] + [CmdletExample( + Code = @"PS:> Get-PnPProvisioningSite -Sequence $mysequence -Identity 8058ea99-af7b-4bb7-b12a-78f93398041e", + Remarks = "Returns the specified site from the specified sequence", + SortOrder = 2)] + public class GetProvisioningSite : PSCmdlet + { + [Parameter(Mandatory = true, HelpMessage = "The sequence to retrieve the site from", ParameterSetName = ParameterAttribute.AllParameterSets)] + public ProvisioningSequence Sequence; + + [Parameter(Mandatory = false, HelpMessage = "Optional Id of the site", ParameterSetName = ParameterAttribute.AllParameterSets, ValueFromPipeline = true)] + public ProvisioningSitePipeBind Identity; + protected override void ProcessRecord() + { + if (!MyInvocation.BoundParameters.ContainsKey("Identity")) + { + WriteObject(Sequence.SiteCollections, true); + } + else + { + WriteObject(Identity.GetSiteFromSequence(Sequence)); + } + } + } +} diff --git a/Commands/Provisioning/NewProvisioningCommunicationSite.cs b/Commands/Provisioning/NewProvisioningCommunicationSite.cs new file mode 100644 index 000000000..26d811288 --- /dev/null +++ b/Commands/Provisioning/NewProvisioningCommunicationSite.cs @@ -0,0 +1,73 @@ +#if !ONPREMISES +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using System.Linq; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Provisioning +{ + [Cmdlet(VerbsCommon.New, "PnPProvisioningCommunicationSite", SupportsShouldProcess = true)] + [CmdletHelp("Creates a communication site object", + Category = CmdletHelpCategory.Provisioning, SupportedPlatform = CmdletSupportedPlatform.Online)] + [CmdletExample( + Code = @"PS:> $site = New-PnPProvisioningCommunicationSite -Url ""/sites/mycommunicationsite"" -Title ""My Team Site""", + Remarks = "Creates a new communication site object with the specified variables", + SortOrder = 1)] + public class NewProvisioningCommunicationSite : PSCmdlet + { + [Parameter(Mandatory = true)] + public string Url; + + [Parameter(Mandatory = true)] + public string Title; + + [Parameter(Mandatory = false)] + public uint Language; + + [Parameter(Mandatory = false)] + public string Owner; + + [Parameter(Mandatory = false)] + public string Description; + + [Parameter(Mandatory = false)] + public string Classification; + + [Parameter(Mandatory = false)] + public string SiteDesignId; + + [Parameter(Mandatory = false)] + public SwitchParameter HubSite; + + [Parameter(Mandatory = false)] + public SwitchParameter AllowFileSharingForGuestUsers; + + [Parameter(Mandatory = false)] + public string[] TemplateIds; + + protected override void ProcessRecord() + { + var site = new CommunicationSiteCollection + { + Url = Url, + Language = (int)Language, + Owner = Owner, + AllowFileSharingForGuestUsers = AllowFileSharingForGuestUsers.IsPresent, + Classification = Classification, + Description = Description, + IsHubSite = HubSite.IsPresent, + Title = Title, + }; + if(MyInvocation.BoundParameters.ContainsKey("SiteDesignId")) + { + site.SiteDesign = SiteDesignId; + } + if (TemplateIds != null) + { + site.Templates.AddRange(TemplateIds.ToList()); + } + WriteObject(site); + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Provisioning/NewProvisioningHierarchy.cs b/Commands/Provisioning/NewProvisioningHierarchy.cs new file mode 100644 index 000000000..5094b4e04 --- /dev/null +++ b/Commands/Provisioning/NewProvisioningHierarchy.cs @@ -0,0 +1,40 @@ +#if !ONPREMISES +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Provisioning +{ + [Cmdlet(VerbsCommon.New, "PnPProvisioningHierarchy", SupportsShouldProcess = true)] + [CmdletHelp("Creates a new provisioning hierarchy object", + Category = CmdletHelpCategory.Provisioning, SupportedPlatform = CmdletSupportedPlatform.Online)] + [CmdletExample( + Code = @"PS:> $hierarchy = New-PnPProvisioningHierarchy", + Remarks = "Creates a new instance of a provisioning hierarchy object.", + SortOrder = 1)] + public class NewProvisioningHierarchy : PSCmdlet + { + [Parameter(Mandatory = false)] + public string Author; + + [Parameter(Mandatory = false)] + public string Description; + + [Parameter(Mandatory = false)] + public string DisplayName; + + [Parameter(Mandatory = false)] + public string Generator; + + protected override void ProcessRecord() + { + var result = new ProvisioningHierarchy(); + result.Author = Author; + result.Description = Description; + result.DisplayName = DisplayName; + result.Generator = Generator; + WriteObject(result); + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Provisioning/NewProvisioningSequence.cs b/Commands/Provisioning/NewProvisioningSequence.cs new file mode 100644 index 000000000..649aa803a --- /dev/null +++ b/Commands/Provisioning/NewProvisioningSequence.cs @@ -0,0 +1,38 @@ +#if !ONPREMISES +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using System; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Provisioning +{ + [Cmdlet(VerbsCommon.New, "PnPProvisioningSequence", SupportsShouldProcess = true)] + [CmdletHelp("Creates a new provisioning sequence object", + Category = CmdletHelpCategory.Provisioning, SupportedPlatform = CmdletSupportedPlatform.Online)] + [CmdletExample( + Code = @"PS:> $sequence = New-PnPProvisioningSequence", + Remarks = "Creates a new instance of a provisioning sequence object.", + SortOrder = 1)] + [CmdletExample( + Code = @"PS:> $sequence = New-PnPProvisioningSequence -Id ""MySequence""", + Remarks = "Creates a new instance of a provisioning sequence object and sets the Id to the value specified.", + SortOrder = 2)] + public class NewProvisioningSequence : PSCmdlet + { + [Parameter(Mandatory = false, HelpMessage = "Optional Id of the sequence", ParameterSetName = ParameterAttribute.AllParameterSets)] + public string Id; + protected override void ProcessRecord() + { + var result = new ProvisioningSequence(); + if (this.MyInvocation.BoundParameters.ContainsKey("Id")) + { + result.ID = Id; + } else + { + result.ID = $"sequence-{Guid.NewGuid()}"; + } + WriteObject(result); + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Provisioning/NewProvisioningTeamNoGroupSite.cs b/Commands/Provisioning/NewProvisioningTeamNoGroupSite.cs new file mode 100644 index 000000000..759873a4b --- /dev/null +++ b/Commands/Provisioning/NewProvisioningTeamNoGroupSite.cs @@ -0,0 +1,62 @@ +#if !ONPREMISES +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using System.Linq; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Provisioning +{ + [Cmdlet(VerbsCommon.New, "PnPProvisioningTeamNoGroupSite", SupportsShouldProcess = true)] + [CmdletHelp("Creates a team site without an Office 365 group object", + Category = CmdletHelpCategory.Provisioning, SupportedPlatform = CmdletSupportedPlatform.Online)] + [CmdletExample( + Code = @"PS:> $site = New-PnPProvisioningTeamNoGroupSite -Alias ""MyTeamSite"" -Title ""My Team Site""", + Remarks = "Creates a new team site object with the specified variables", + SortOrder = 1)] + public class NewProvisioningTeamNoGroupSite : PSCmdlet + { + [Parameter(Mandatory = true)] + public string Url; + + [Parameter(Mandatory = true)] + public string Title; + + [Parameter(Mandatory = true)] + public uint TimeZoneId; + + [Parameter(Mandatory = false)] + public uint Language; + + [Parameter(Mandatory = false)] + public string Owner; + + [Parameter(Mandatory = false)] + public string Description; + + [Parameter(Mandatory = false)] + public SwitchParameter HubSite; + + [Parameter(Mandatory = false)] + public string[] TemplateIds; + + protected override void ProcessRecord() + { + var site = new TeamNoGroupSiteCollection + { + Url = Url, + Language = (int)Language, + Owner = Owner, + TimeZoneId = (int)TimeZoneId, + Description = Description, + IsHubSite = HubSite.IsPresent, + Title = Title + }; + if (TemplateIds != null) + { + site.Templates.AddRange(TemplateIds.ToList()); + } + WriteObject(site); + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Provisioning/NewProvisioningTeamNoGroupSubSite.cs b/Commands/Provisioning/NewProvisioningTeamNoGroupSubSite.cs new file mode 100644 index 000000000..9918c1469 --- /dev/null +++ b/Commands/Provisioning/NewProvisioningTeamNoGroupSubSite.cs @@ -0,0 +1,64 @@ +#if !ONPREMISES +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using System.Linq; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Provisioning +{ + [Cmdlet(VerbsCommon.New, "PnPProvisioningTeamNoGroupSubSite", SupportsShouldProcess = true)] + [CmdletHelp("Creates a team site subsite with no Office 365 group object", + Category = CmdletHelpCategory.Provisioning, SupportedPlatform = CmdletSupportedPlatform.Online)] + [CmdletExample( + Code = @"PS:> $site = New-PnPProvisioningTeamNoGroupSubSite -Url ""MyTeamSubsite"" -Title ""My Team Site"" -TimeZoneId 4", + Remarks = "Creates a new team site subsite object with the specified variables", + SortOrder = 1)] + public class NewProvisioningTeamNoGroupSubSite : PSCmdlet + { + + [Parameter(Mandatory = true)] + public string Url; + + [Parameter(Mandatory = true)] + public string Title; + + [Parameter(Mandatory = true)] + public uint TimeZoneId; + + [Parameter(Mandatory = false)] + public uint Language; + + [Parameter(Mandatory = false)] + public string Description; + + [Parameter(Mandatory = false)] + public string[] TemplateIds; + + [Parameter(Mandatory = false)] + public SwitchParameter QuickLaunchDisabled; + + [Parameter(Mandatory = false)] + public SwitchParameter UseDifferentPermissionsFromParentSite; + + protected override void ProcessRecord() + { + SiteCollection c; + var site = new TeamNoGroupSubSite() + { + Url = Url, + Language = (int)Language, + QuickLaunchEnabled = !QuickLaunchDisabled.IsPresent, + UseSamePermissionsAsParentSite = !UseDifferentPermissionsFromParentSite.IsPresent, + TimeZoneId = (int)TimeZoneId, + Description = Description, + Title = Title + }; + if (TemplateIds != null) + { + site.Templates.AddRange(TemplateIds.ToList()); + } + WriteObject(site); + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Provisioning/NewProvisioningTeamSite.cs b/Commands/Provisioning/NewProvisioningTeamSite.cs new file mode 100644 index 000000000..467e56ff6 --- /dev/null +++ b/Commands/Provisioning/NewProvisioningTeamSite.cs @@ -0,0 +1,63 @@ +#if !ONPREMISES +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using System.Linq; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Provisioning +{ + [Cmdlet(VerbsCommon.New, "PnPProvisioningTeamSite", SupportsShouldProcess = true)] + [CmdletHelp("Creates a team site object", + Category = CmdletHelpCategory.Provisioning, SupportedPlatform = CmdletSupportedPlatform.Online)] + [CmdletExample( + Code = @"PS:> $site = New-PnPProvisioningTeamSite -Alias ""MyTeamSite"" -Title ""My Team Site""", + Remarks = "Creates a new team site object with the specified variables", + SortOrder = 1)] + public class NewProvisioningTeamSite : PSCmdlet + { + + [Parameter(Mandatory = true)] + public string Alias; + + [Parameter(Mandatory = true)] + public string Title; + + [Parameter(Mandatory = false)] + public string Description = ""; + + [Parameter(Mandatory = false)] + public string DisplayName = ""; + + [Parameter(Mandatory = false)] + public string Classification; + + [Parameter(Mandatory = false)] + public SwitchParameter Public; + + [Parameter(Mandatory = false)] + public SwitchParameter HubSite; + + [Parameter(Mandatory = false)] + public string[] TemplateIds; + + protected override void ProcessRecord() + { + var site = new TeamSiteCollection + { + Alias = Alias, + Classification = Classification, + Description = Description, + DisplayName = DisplayName, + IsHubSite = HubSite.IsPresent, + IsPublic = Public.IsPresent, + Title = Title + }; + if (TemplateIds != null) + { + site.Templates.AddRange(TemplateIds.ToList()); + } + WriteObject(site); + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Provisioning/NewProvisioningTemplate.cs b/Commands/Provisioning/NewProvisioningTemplate.cs index 8cd226ce2..a9d0ae020 100644 --- a/Commands/Provisioning/NewProvisioningTemplate.cs +++ b/Commands/Provisioning/NewProvisioningTemplate.cs @@ -11,9 +11,9 @@ namespace SharePointPnP.PowerShell.Commands.Provisioning Code = @"PS:> $template = New-PnPProvisioningTemplate", Remarks = "Creates a new instance of a provisioning template object.", SortOrder = 1)] - public class NewProvisioningTemplate : PnPWebCmdlet + public class NewProvisioningTemplate : PSCmdlet { - protected override void ExecuteCmdlet() + protected override void ProcessRecord() { var result = new ProvisioningTemplate(); WriteObject(result); diff --git a/Commands/Provisioning/ReadProvisioningHierarchy.cs b/Commands/Provisioning/ReadProvisioningHierarchy.cs new file mode 100644 index 000000000..7c9fb7bd1 --- /dev/null +++ b/Commands/Provisioning/ReadProvisioningHierarchy.cs @@ -0,0 +1,79 @@ +#if !ONPREMISES +using OfficeDevPnP.Core.Framework.Provisioning.Connectors; +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using OfficeDevPnP.Core.Framework.Provisioning.Providers; +using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Utilities; +using System; +using System.IO; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Provisioning +{ + [Cmdlet(VerbsCommunications.Read, "PnPProvisioningHierarchy")] + [CmdletHelp("Loads/Reads a PnP provisioning hierarchy from the file system and returns an in-memory instance of this template.", + Category = CmdletHelpCategory.Provisioning, SupportedPlatform = CmdletSupportedPlatform.Online)] + [CmdletExample( + Code = @"PS:> Read-PnPProvisioningHierarchy -Path hierarchy.pnp", + Remarks = "Reads a PnP provisioning hierarchy file from the file system and returns an in-memory instance", + SortOrder = 1)] + public class ReadProvisioningHierarchy : PSCmdlet + { + [Parameter(Mandatory = true, Position = 0, HelpMessage = "Filename to read from, optionally including full path.")] + public string Path; + + [Parameter(Mandatory = false, HelpMessage = "Allows you to specify ITemplateProviderExtension to execute while loading the template.")] + public ITemplateProviderExtension[] TemplateProviderExtensions; + + protected override void ProcessRecord() + { + if (!System.IO.Path.IsPathRooted(Path)) + { + Path = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, Path); + } + WriteObject(LoadProvisioningHierarchyFromFile(Path, TemplateProviderExtensions)); + } + + internal static ProvisioningHierarchy LoadProvisioningHierarchyFromFile(string templatePath, ITemplateProviderExtension[] templateProviderExtensions) + { + // Prepare the File Connector + string templateFileName = System.IO.Path.GetFileName(templatePath); + + // Prepare the template path + var fileInfo = new FileInfo(templatePath); + FileConnectorBase fileConnector = new FileSystemConnector(fileInfo.DirectoryName, ""); + + // Load the provisioning template file + var isOpenOfficeFile = false; + using (var stream = fileConnector.GetFileStream(templateFileName)) + { + isOpenOfficeFile = FileUtilities.IsOpenOfficeFile(stream); + } + + XMLTemplateProvider provider; + if (isOpenOfficeFile) + { + provider = new XMLOpenXMLTemplateProvider(new OpenXMLConnector(templateFileName, fileConnector)); + templateFileName = templateFileName.Substring(0, templateFileName.LastIndexOf(".", StringComparison.Ordinal)) + ".xml"; + + var hierarchy = (provider as XMLOpenXMLTemplateProvider).GetHierarchy(); + if (hierarchy != null) + { + hierarchy.Connector = provider.Connector; + return hierarchy; + } + } + else + { + provider = new XMLFileSystemTemplateProvider(fileConnector.Parameters[FileConnectorBase.CONNECTIONSTRING] + "", ""); + } + + ProvisioningHierarchy provisioningHierarchy = provider.GetHierarchy(templateFileName); + provisioningHierarchy.Connector = provider.Connector; + // Return the result + return provisioningHierarchy; + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Provisioning/ReadProvisioningTemplate.cs b/Commands/Provisioning/ReadProvisioningTemplate.cs index b79461ae5..8c759e2aa 100644 --- a/Commands/Provisioning/ReadProvisioningTemplate.cs +++ b/Commands/Provisioning/ReadProvisioningTemplate.cs @@ -3,6 +3,7 @@ using OfficeDevPnP.Core.Framework.Provisioning.Providers; using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml; using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Utilities; using System; using System.IO; using System.Management.Automation; @@ -16,11 +17,11 @@ namespace SharePointPnP.PowerShell.Commands.Provisioning Category = CmdletHelpCategory.Provisioning)] [CmdletExample( Code = @"PS:> Read-PnPProvisioningTemplate -Path template.pnp", - Remarks = "Loads a PnP file from the file systems", + Remarks = "Loads a PnP file from the file system", SortOrder = 1)] [CmdletExample( Code = @"PS:> Read-PnPProvisioningTemplate -Path template.pnp -TemplateProviderExtensions $extensions", - Remarks = "Loads a PnP file from the file systems using some custom template provider extenions while loading the file.", + Remarks = "Loads a PnP file from the file system using some custom template provider extenions while loading the file.", SortOrder = 2)] public class ReadProvisioningTemplate : PSCmdlet { @@ -54,7 +55,7 @@ internal static ProvisioningTemplate LoadProvisioningTemplateFromFile(string tem // Load the provisioning template file Stream stream = fileConnector.GetFileStream(templateFileName); - var isOpenOfficeFile = ApplyProvisioningTemplate.IsOpenOfficeFile(stream); + var isOpenOfficeFile = FileUtilities.IsOpenOfficeFile(stream); XMLTemplateProvider provider; if (isOpenOfficeFile) diff --git a/Commands/Provisioning/SaveProvisioningHierarchy.cs b/Commands/Provisioning/SaveProvisioningHierarchy.cs new file mode 100644 index 000000000..2672f45b3 --- /dev/null +++ b/Commands/Provisioning/SaveProvisioningHierarchy.cs @@ -0,0 +1,172 @@ +#if !ONPREMISES +using OfficeDevPnP.Core.Framework.Provisioning.Connectors; +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using OfficeDevPnP.Core.Framework.Provisioning.Providers; +using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using System; +using System.IO; +using System.Linq; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Provisioning +{ + [Cmdlet(VerbsData.Save, "PnPProvisioningHierarchy")] + [CmdletHelp("Saves a PnP provisioning hierarchy to the file system", + Category = CmdletHelpCategory.Provisioning, SupportedPlatform = CmdletSupportedPlatform.Online)] + [CmdletExample( + Code = @"PS:> Save-PnPProvisioningHierarchy -Hierarchy $hierarchy -Out .\hierarchy.pnp", + Remarks = "Saves a PnP provisioning hiearchy to the file system", + SortOrder = 1)] + public class SaveProvisioningHierarchy : PSCmdlet + { + [Parameter(Mandatory = true, HelpMessage = "Allows you to provide an in-memory instance of the ProvisioningHierarchy type of the PnP Core Component. When using this parameter, the -Out parameter refers to the path for saving the template and storing any supporting file for the template.")] + public ProvisioningHierarchy Hierarchy; + + [Parameter(Mandatory = true, Position = 0, HelpMessage = "Filename to write to, optionally including full path.")] + public string Out; + + [Parameter(Mandatory = false, HelpMessage = "Specifying the Force parameter will skip the confirmation question.")] + public SwitchParameter Force; + + //[Parameter(Mandatory = false, HelpMessage = "Allows you to specify the ITemplateProviderExtension to execute while saving a template.")] + //public ITemplateProviderExtension[] TemplateProviderExtensions; + + protected override void ProcessRecord() + { + // Determine the output file name and path + string outFileName = Path.GetFileName(Out); + + if (!Path.IsPathRooted(Out)) + { + Out = Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, Out); + } + + bool proceed = false; + + if (System.IO.File.Exists(Out)) + { + if (Force || ShouldContinue(string.Format(Properties.Resources.File0ExistsOverwrite, Out), + Properties.Resources.Confirm)) + { + System.IO.File.Delete(Out); + proceed = true; + } + } + else + { + proceed = true; + } + + string outPath = new FileInfo(Out).DirectoryName; + + // Determine if it is an .XML or a .PNP file + var extension = ""; + if (proceed && outFileName != null) + { + if (outFileName.IndexOf(".", StringComparison.Ordinal) > -1) + { + extension = outFileName.Substring(outFileName.LastIndexOf(".", StringComparison.Ordinal)).ToLower(); + } + else + { + extension = ".pnp"; + } + } + + var fileSystemConnector = new FileSystemConnector(outPath, ""); + + ITemplateFormatter formatter = XMLPnPSchemaFormatter.LatestFormatter; + + if (extension == ".pnp") + { + // var connector = new OpenXMLConnector(Out, fileSystemConnector); + var templateFileName = outFileName.Substring(0, outFileName.LastIndexOf(".", StringComparison.Ordinal)) + ".xml"; + + XMLTemplateProvider provider = new XMLOpenXMLTemplateProvider( + Out, fileSystemConnector, templateFileName: templateFileName); + WriteObject("Processing template"); + provider.SaveAs(Hierarchy, templateFileName); + ProcessFiles(Out, fileSystemConnector, provider.Connector); + + //provider.SaveAs(Hierarchy, templateFileName); + //connector.Commit(); + } + else + { + XMLTemplateProvider provider = new XMLFileSystemTemplateProvider(outPath, ""); + provider.SaveAs(Hierarchy, Out); + } + } + + private void ProcessFiles(string templateFileName, FileConnectorBase fileSystemConnector, FileConnectorBase connector) + { + var hierarchy = ReadProvisioningHierarchy.LoadProvisioningHierarchyFromFile(templateFileName, null); + if (Hierarchy.Tenant?.AppCatalog != null) + { + foreach (var app in Hierarchy.Tenant.AppCatalog.Packages) + { + WriteObject($"Processing {app.Src}"); + AddFile(app.Src, hierarchy, fileSystemConnector, connector); + } + } + if (Hierarchy.Tenant?.SiteScripts != null) + { + foreach (var siteScript in Hierarchy.Tenant.SiteScripts) + { + WriteObject($"Processing {siteScript.JsonFilePath}"); + AddFile(siteScript.JsonFilePath, hierarchy, fileSystemConnector, connector); + } + } + if (Hierarchy.Localizations != null && Hierarchy.Localizations.Any()) + { + foreach (var location in Hierarchy.Localizations) + { + WriteObject($"Processing {location.ResourceFile}"); + AddFile(location.ResourceFile, hierarchy, fileSystemConnector, connector); + } + } + foreach (var template in Hierarchy.Templates) + { + if(template.WebSettings != null && template.WebSettings.SiteLogo != null) + { + // is it a file? + var isFile = false; + using (var fileStream = fileSystemConnector.GetFileStream(template.WebSettings.SiteLogo)) + { + isFile = fileStream != null; + } + if (isFile) + { + WriteObject($"Processing {template.WebSettings.SiteLogo}"); + AddFile(template.WebSettings.SiteLogo, hierarchy, fileSystemConnector, connector); + } + } + if (template.Files.Any()) + { + foreach (var file in template.Files) + { + AddFile(file.Src, hierarchy, fileSystemConnector, connector); + } + } + } + + } + + private void AddFile(string sourceName, ProvisioningHierarchy hierarchy, FileConnectorBase fileSystemConnector, FileConnectorBase connector) + { + using (var fs = fileSystemConnector.GetFileStream(sourceName)) + { + var fileName = sourceName.IndexOf("\\") > 0 ? sourceName.Substring(sourceName.LastIndexOf("\\") + 1) : sourceName; + var folderName = sourceName.IndexOf("\\") > 0 ? sourceName.Substring(0, sourceName.LastIndexOf("\\")) : ""; + hierarchy.Connector.SaveFileStream(fileName, folderName, fs); + + if (hierarchy.Connector is ICommitableFileConnector) + { + ((ICommitableFileConnector)hierarchy.Connector).Commit(); + } + } + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Provisioning/SaveProvisioningTemplate.cs b/Commands/Provisioning/SaveProvisioningTemplate.cs index 5fd1f6f24..035941840 100644 --- a/Commands/Provisioning/SaveProvisioningTemplate.cs +++ b/Commands/Provisioning/SaveProvisioningTemplate.cs @@ -14,11 +14,11 @@ namespace SharePointPnP.PowerShell.Commands.Provisioning { [Cmdlet(VerbsData.Save, "PnPProvisioningTemplate")] - [CmdletHelp("Saves a PnP file to the file systems", + [CmdletHelp("Saves a PnP provisioning tempalte to the file system", Category = CmdletHelpCategory.Provisioning)] [CmdletExample( Code = @"PS:> Save-PnPProvisioningTemplate -InputInstance $template -Out .\template.pnp", - Remarks = "Saves a PnP file to the file systems", + Remarks = "Saves a PnP provisioning template to the file system as a PnP file.", SortOrder = 1)] public class SaveProvisioningTemplate : PSCmdlet { diff --git a/Commands/Provisioning/SetProvisioningTemplateMetadata.cs b/Commands/Provisioning/SetProvisioningTemplateMetadata.cs index 6806df4e9..5e4508e9e 100644 --- a/Commands/Provisioning/SetProvisioningTemplateMetadata.cs +++ b/Commands/Provisioning/SetProvisioningTemplateMetadata.cs @@ -8,6 +8,7 @@ using OfficeDevPnP.Core.Framework.Provisioning.Connectors; using System.Collections; using OfficeDevPnP.Core.Framework.Provisioning.Providers; +using SharePointPnP.PowerShell.Commands.Utilities; namespace SharePointPnP.PowerShell.Commands.Provisioning { @@ -86,7 +87,7 @@ protected override void ExecuteCmdlet() XMLTemplateProvider provider; ProvisioningTemplate provisioningTemplate; Stream stream = fileConnector.GetFileStream(templateFileName); - var isOpenOfficeFile = ApplyProvisioningTemplate.IsOpenOfficeFile(stream); + var isOpenOfficeFile = FileUtilities.IsOpenOfficeFile(stream); if (isOpenOfficeFile) { provider = new XMLOpenXMLTemplateProvider(new OpenXMLConnector(templateFileName, fileConnector)); diff --git a/Commands/Provisioning/TestProvisioningHierarchy.cs b/Commands/Provisioning/TestProvisioningHierarchy.cs new file mode 100644 index 000000000..b339ff341 --- /dev/null +++ b/Commands/Provisioning/TestProvisioningHierarchy.cs @@ -0,0 +1,91 @@ +using OfficeDevPnP.Core.Framework.Provisioning.Model; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Provisioning +{ + [Cmdlet(VerbsDiagnostic.Test, "PnPProvisioningHierarchy", SupportsShouldProcess = true)] + [CmdletHelp("Tests a provisioning hierarchy for invalid references", + Category = CmdletHelpCategory.Provisioning)] + [CmdletExample( + Code = @"PS:> Test-PnPProvisioningHierarchy -Hierarchy $myhierarchy", + Remarks = "Checks for valid template references", + SortOrder = 1)] + public class TestProvisioningHierarchy : PnPCmdlet + { + [Parameter(Mandatory = true, HelpMessage = "The hierarchy to add the sequence to", ParameterSetName = ParameterAttribute.AllParameterSets)] + public ProvisioningHierarchy Hierarchy; + + protected override void ExecuteCmdlet() + { + List issues = new List(); + foreach(var sequence in Hierarchy.Sequences) + { + foreach (var site in sequence.SiteCollections) + { + foreach (var template in site.Templates) + { + if(Hierarchy.Templates.FirstOrDefault(t => t.Id == template) == null) + { + issues.Add($"Template {template} referenced in site {site.Id} is not present in hierarchy."); + } + } + foreach(var subsite in site.Sites.Cast()) + { + foreach (var template in subsite.Templates) + { + if (Hierarchy.Templates.FirstOrDefault(t => t.Id == template) == null) + { + issues.Add($"Template {template} referenced in subsite with url {subsite.Url} in site {site.Id} is not present in hierarchy"); + } + } + } + } + } + foreach(var template in Hierarchy.Templates) + { + var used = false; + foreach(var sequence in Hierarchy.Sequences) + { + foreach(var site in sequence.SiteCollections) + { + if(site.Templates.Contains(template.Id)) + { + used = true; + break; + } + + foreach(var subsite in site.Sites) + { + if(subsite.Templates.Contains(template.Id)) + { + used = true; + break; + } + } + if (used) + { + break; + } + } + if(used) + { + break; + } + } + if(!used) + { + issues.Add($"Template {template.Id} is not used by any site in the hierarchy."); + } + } + if(issues.Any()) + { + WriteObject(issues, true); + } + + } + } +} diff --git a/Commands/SharePointPnP.PowerShell.Commands.csproj b/Commands/SharePointPnP.PowerShell.Commands.csproj index 5edb933f0..fb50e70a3 100644 --- a/Commands/SharePointPnP.PowerShell.Commands.csproj +++ b/Commands/SharePointPnP.PowerShell.Commands.csproj @@ -459,11 +459,14 @@ + + + @@ -478,6 +481,7 @@ + @@ -497,6 +501,22 @@ + + + + + + + + + + + + + + + + @@ -642,6 +662,7 @@ + @@ -771,7 +792,7 @@ - + diff --git a/Commands/Site/SetPnPSite.cs b/Commands/Site/SetPnPSite.cs deleted file mode 100644 index 94343afda..000000000 --- a/Commands/Site/SetPnPSite.cs +++ /dev/null @@ -1,55 +0,0 @@ -#if !ONPREMISES -using System; -using System.Linq.Expressions; -using System.Management.Automation; -using Microsoft.SharePoint.Client; -using SharePointPnP.PowerShell.CmdletHelpAttributes; - -namespace SharePointPnP.PowerShell.Commands.Site -{ - [Cmdlet(VerbsCommon.Set, "PnPSite")] - [CmdletHelp("Sets Site Collection properties.", - Category = CmdletHelpCategory.Sites, - SupportedPlatform = CmdletSupportedPlatform.Online)] - [CmdletExample( - Code = @"PS:> Set-PnPSite -Classification ""HBI""", - Remarks = "Sets the current site classification to HBI", - SortOrder = 1)] - [CmdletExample( - Code = @"PS:> Set-PnPSite -Classification $null", - Remarks = "Unsets the current site classification", - SortOrder = 1)] - [CmdletExample( - Code = @"PS:> Set-PnPSite -DisableFlows", - Remarks = "Disables Flows for this site", - SortOrder = 2)] - [CmdletExample( - Code = @"PS:> Set-PnPSite -DisableFlows:$false", - Remarks = "Enables Flows for this site", - SortOrder = 3)] - public class SetSite : PnPCmdlet - { - [Parameter(Mandatory = false, HelpMessage = "The classification to set")] - public string Classification; - - [Parameter(Mandatory = false, HelpMessage = "Disables flows for this site")] - public SwitchParameter DisableFlows; - - protected override void ExecuteCmdlet() - { - var site = ClientContext.Site; - if (MyInvocation.BoundParameters.ContainsKey("Classification")) - { - site.Classification = Classification; - } - if (MyInvocation.BoundParameters.ContainsKey("DisableFlows")) - { - site.DisableFlows = DisableFlows; - } - - - ClientContext.ExecuteQueryRetry(); - } - } -} -#endif \ No newline at end of file diff --git a/Commands/Site/SetSite.cs b/Commands/Site/SetSite.cs new file mode 100644 index 000000000..68fdbbe5f --- /dev/null +++ b/Commands/Site/SetSite.cs @@ -0,0 +1,88 @@ +#if !ONPREMISES +using Microsoft.SharePoint.Client; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Site +{ + [Cmdlet(VerbsCommon.Set, "PnPSite")] + [CmdletHelp("Sets Site Collection properties.", + Category = CmdletHelpCategory.Sites, + SupportedPlatform = CmdletSupportedPlatform.Online)] + [CmdletExample( + Code = @"PS:> Set-PnPSite -Classification ""HBI""", + Remarks = "Sets the current site classification to HBI", + SortOrder = 1)] + [CmdletExample( + Code = @"PS:> Set-PnPSite -Classification $null", + Remarks = "Unsets the current site classification", + SortOrder = 1)] + [CmdletExample( + Code = @"PS:> Set-PnPSite -DisableFlows", + Remarks = "Disables Flows for this site", + SortOrder = 2)] + [CmdletExample( + Code = @"PS:> Set-PnPSite -DisableFlows:$false", + Remarks = "Enables Flows for this site", + SortOrder = 3)] + [CmdletExample( + Code = @"PS:> Set-PnPSite -SiteLogoPath c:\images\mylogo.png", + Remarks = "Sets the logo if the site is a modern team site", + SortOrder = 4)] + public class SetSite : PnPCmdlet + { + [Parameter(Mandatory = false, HelpMessage = "The classification to set")] + public string Classification; + + [Parameter(Mandatory = false, HelpMessage = "Disables flows for this site")] + public SwitchParameter DisableFlows; + + [Parameter(Mandatory = false, HelpMessage = "Sets the logo if the site is modern team site. If you want to set the logo for a classic site, use Set-PnPWeb -SiteLogoUrl")] + public string LogoFilePath; + + protected override void ExecuteCmdlet() + { + var executeQueryRequired = false; + var site = ClientContext.Site; + if (MyInvocation.BoundParameters.ContainsKey("Classification")) + { + site.Classification = Classification; + executeQueryRequired = true; + } + if (MyInvocation.BoundParameters.ContainsKey("DisableFlows")) + { + site.DisableFlows = DisableFlows; + executeQueryRequired = true; + } + if (MyInvocation.BoundParameters.ContainsKey("LogoFilePath")) + { + var webTemplate = ClientContext.Web.EnsureProperty(w => w.WebTemplate); + if (webTemplate == "GROUP") + { + if (!System.IO.Path.IsPathRooted(LogoFilePath)) + { + LogoFilePath = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, LogoFilePath); + } + if (System.IO.File.Exists(LogoFilePath)) + { + var bytes = System.IO.File.ReadAllBytes(LogoFilePath); + var mimeType = System.Web.MimeMapping.GetMimeMapping(LogoFilePath); + var result = OfficeDevPnP.Core.Sites.SiteCollection.SetGroupImage(ClientContext, bytes, mimeType).GetAwaiter().GetResult(); + } + else + { + throw new System.Exception("Logo file does not exist"); + } + } else + { + throw new System.Exception("Not an Office365 group enabled site."); + } + } + if (executeQueryRequired) + { + ClientContext.ExecuteQueryRetry(); + } + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Taxonomy/GetTaxonomySession.cs b/Commands/Taxonomy/GetTaxonomySession.cs index ca42fa9f3..758991de1 100644 --- a/Commands/Taxonomy/GetTaxonomySession.cs +++ b/Commands/Taxonomy/GetTaxonomySession.cs @@ -14,7 +14,10 @@ public class GetTaxonomySession : PnPWebCmdlet { protected override void ExecuteCmdlet() { - WriteObject(ClientContext.Site.GetTaxonomySession()); + var session = ClientContext.Site.GetTaxonomySession(); + ClientContext.Load(session.TermStores); + ClientContext.ExecuteQueryRetry(); + WriteObject(session); } } diff --git a/Commands/Taxonomy/GetTerm.cs b/Commands/Taxonomy/GetTerm.cs index f9c2b47dc..6282a4cfd 100644 --- a/Commands/Taxonomy/GetTerm.cs +++ b/Commands/Taxonomy/GetTerm.cs @@ -1,11 +1,11 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Management.Automation; -using Microsoft.SharePoint.Client; +using Microsoft.SharePoint.Client; using Microsoft.SharePoint.Client.Taxonomy; using SharePointPnP.PowerShell.CmdletHelpAttributes; using SharePointPnP.PowerShell.Commands.Base.PipeBinds; +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Management.Automation; namespace SharePointPnP.PowerShell.Commands.Taxonomy { @@ -32,24 +32,33 @@ namespace SharePointPnP.PowerShell.Commands.Taxonomy SortOrder = 2)] public class GetTerm : PnPRetrievalsCmdlet { - [Parameter(Mandatory = false, HelpMessage = "The Id or Name of a Term")] + private const string ParameterSet_TERM = "By Term Id"; + private const string ParameterSet_TERMSET = "By Termset"; + private Term term; + + [Parameter(Mandatory = true, HelpMessage = "The Id or Name of a Term", ParameterSetName = ParameterSet_TERM)] + [Parameter(Mandatory = false, HelpMessage = "The Id or Name of a Term", ParameterSetName = ParameterSet_TERMSET)] public GenericObjectNameIdPipeBind Identity; - [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0, HelpMessage = "Name of the termset to check.")] + [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0, HelpMessage = "Name of the termset to check.", ParameterSetName = ParameterSet_TERMSET)] public TaxonomyItemPipeBind TermSet; - [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0, HelpMessage = "Name of the termgroup to check.")] + [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0, HelpMessage = "Name of the termgroup to check.", ParameterSetName = ParameterSet_TERMSET)] public TermGroupPipeBind TermGroup; - [Parameter(Mandatory = false, HelpMessage = "Term store to check; if not specified the default term store is used.")] + [Parameter(Mandatory = false, HelpMessage = "Term store to check; if not specified the default term store is used.", ParameterSetName = ParameterSet_TERM)] + [Parameter(Mandatory = false, HelpMessage = "Term store to check; if not specified the default term store is used.", ParameterSetName = ParameterSet_TERMSET)] public GenericObjectNameIdPipeBind TermStore; - [Parameter(Mandatory = false, HelpMessage = "Find the first term recursivly matching the label in a term hierarchy.")] + [Parameter(Mandatory = false, HelpMessage = "Find the first term recursivly matching the label in a term hierarchy.", ParameterSetName = ParameterSet_TERMSET)] public SwitchParameter Recursive; + [Parameter(Mandatory = false, HelpMessage = "Includes the hierarchy of child terms if available", ParameterSetName = ParameterAttribute.AllParameterSets)] + public SwitchParameter IncludeChildTerms; + protected override void ExecuteCmdlet() { - DefaultRetrievalExpressions = new Expression>[] { g => g.Name, g => g.Id }; + DefaultRetrievalExpressions = new Expression>[] { g => g.Name, g => g.TermsCount, g => g.Id }; var taxonomySession = TaxonomySession.GetTaxonomySession(ClientContext); // Get Term Store TermStore termStore = null; @@ -76,72 +85,122 @@ protected override void ExecuteCmdlet() } } - TermGroup termGroup = null; - - if (TermGroup.Id != Guid.Empty) - { - termGroup = termStore.Groups.GetById(TermGroup.Id); - } - else if (!string.IsNullOrEmpty(TermGroup.Name)) - { - termGroup = termStore.Groups.GetByName(TermGroup.Name); - } - - TermSet termSet; - if (TermSet.Id != Guid.Empty) + if (ParameterSetName == ParameterSet_TERM) { - termSet = termGroup.TermSets.GetById(TermSet.Id); - } - else if (!string.IsNullOrEmpty(TermSet.Title)) - { - termSet = termGroup.TermSets.GetByName(TermSet.Title); + if (Identity.IdValue != Guid.Empty) + { + term = termStore.GetTerm(Identity.IdValue); + ClientContext.Load(term, RetrievalExpressions); + ClientContext.ExecuteQueryRetry(); + if (IncludeChildTerms.IsPresent && term.TermsCount > 0) + { + LoadChildTerms(term); + } + WriteObject(term); + } else + { + WriteError(new ErrorRecord(new Exception("Insuffication parameters"), "INSUFFICIENTPARAMETERS", ErrorCategory.SyntaxError, this)); + } } else { - termSet = TermSet.Item; - } - if (Identity != null) - { - Term term = null; - if (Identity.IdValue != Guid.Empty) + TermGroup termGroup = null; + + if (TermGroup != null && TermGroup.Id != Guid.Empty) { - term = termSet.Terms.GetById(Identity.IdValue); + termGroup = termStore.Groups.GetById(TermGroup.Id); } - else + else if (TermGroup != null && !string.IsNullOrEmpty(TermGroup.Name)) { - var termName = TaxonomyExtensions.NormalizeName(Identity.StringValue); - if (!Recursive) + termGroup = termStore.Groups.GetByName(TermGroup.Name); + } + + TermSet termSet = null; + if (TermSet != null) + { + if (TermSet.Id != Guid.Empty) + { + termSet = termGroup.TermSets.GetById(TermSet.Id); + } + else if (!string.IsNullOrEmpty(TermSet.Title)) + { + termSet = termGroup.TermSets.GetByName(TermSet.Title); + } + else { - term = termSet.Terms.GetByName(termName); + termSet = TermSet.Item; + } + } + if (Identity != null) + { + term = null; + if (Identity.IdValue != Guid.Empty) + { + term = termStore.GetTerm(Identity.IdValue); } else { - var lmi = new LabelMatchInformation(ClientContext) + var termName = TaxonomyExtensions.NormalizeName(Identity.StringValue); + if (!Recursive) { - TrimUnavailable = true, - TermLabel = termName - }; + term = termSet.Terms.GetByName(termName); + } + else + { + var lmi = new LabelMatchInformation(ClientContext) + { + TrimUnavailable = true, + TermLabel = termName + }; - var termMatches = termSet.GetTerms(lmi); - ClientContext.Load(termMatches); - ClientContext.ExecuteQueryRetry(); + var termMatches = termSet.GetTerms(lmi); + ClientContext.Load(termMatches); + ClientContext.ExecuteQueryRetry(); - if (termMatches.AreItemsAvailable) + if (termMatches.AreItemsAvailable) + { + term = termMatches.FirstOrDefault(); + } + } + } + ClientContext.Load(term, RetrievalExpressions); + ClientContext.ExecuteQueryRetry(); + if (IncludeChildTerms.IsPresent && term.TermsCount > 0) + { + LoadChildTerms(term); + } + WriteObject(term); + } + else + { + var query = termSet.Terms.IncludeWithDefaultProperties(RetrievalExpressions); + var terms = ClientContext.LoadQuery(query); + ClientContext.ExecuteQueryRetry(); + if (IncludeChildTerms.IsPresent) + { + foreach (var collectionTerm in terms) { - term = termMatches.FirstOrDefault(); + if (collectionTerm.TermsCount > 0) + { + LoadChildTerms(collectionTerm); + } } } + WriteObject(terms, true); } - ClientContext.Load(term, RetrievalExpressions); - ClientContext.ExecuteQueryRetry(); - WriteObject(term); } - else + } + + private void LoadChildTerms(Term incomingTerm) + { + ClientContext.Load(incomingTerm.Terms, ts => ts.IncludeWithDefaultProperties(RetrievalExpressions)); + ClientContext.ExecuteQueryRetry(); + foreach (var childTerm in incomingTerm.Terms) { - var query = termSet.Terms.IncludeWithDefaultProperties(RetrievalExpressions); - var terms = ClientContext.LoadQuery(query); - ClientContext.ExecuteQueryRetry(); - WriteObject(terms, true); + if (childTerm.TermsCount > 0) + { + LoadChildTerms(childTerm); + } } } } diff --git a/Commands/Utilities/FileUtilities.cs b/Commands/Utilities/FileUtilities.cs new file mode 100644 index 000000000..98db23b63 --- /dev/null +++ b/Commands/Utilities/FileUtilities.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharePointPnP.PowerShell.Commands.Utilities +{ + public static class FileUtilities + { + internal static bool IsOpenOfficeFile(Stream stream) + { + bool istrue = false; + // SIG 50 4B 03 04 14 00 + + byte[] bytes = new byte[6]; + stream.Read(bytes, 0, 6); + var signature = string.Empty; + foreach (var b in bytes) + { + signature += b.ToString("X2"); + } + if (signature == "504B03041400") + { + istrue = true; + } + return istrue; + } + } +} diff --git a/Commands/Web/SetWeb.cs b/Commands/Web/SetWeb.cs index 2eee363ee..929d42e39 100644 --- a/Commands/Web/SetWeb.cs +++ b/Commands/Web/SetWeb.cs @@ -1,6 +1,6 @@ -using System.Management.Automation; -using Microsoft.SharePoint.Client; +using Microsoft.SharePoint.Client; using SharePointPnP.PowerShell.CmdletHelpAttributes; +using System.Management.Automation; namespace SharePointPnP.PowerShell.Commands { @@ -9,7 +9,7 @@ namespace SharePointPnP.PowerShell.Commands Category = CmdletHelpCategory.Webs)] public class SetWeb : PnPWebCmdlet { - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Sets the logo of the web to the current url. If you want to set the logo to a modern team site, use Set-PnPSite -SiteLogoPath")] public string SiteLogoUrl; [Parameter(Mandatory = false)]