From 969ab7ae257f9bb53ae075c79ed4bd7d4598127f Mon Sep 17 00:00:00 2001 From: reusto <1266516+reusto@users.noreply.github.com> Date: Sun, 26 Mar 2023 01:45:41 +0100 Subject: [PATCH 1/3] Add PreviousNode parameter to Add-PnPNavigationNode --- documentation/Add-PnPNavigationNode.md | 36 ++++- .../Base/PipeBinds/NavigationNodePipeBind.cs | 23 ++- src/Commands/Navigation/AddNavigationNode.cs | 135 ++++++++++-------- .../Navigation/RemoveNavigationNode.cs | 2 +- 4 files changed, 131 insertions(+), 65 deletions(-) diff --git a/documentation/Add-PnPNavigationNode.md b/documentation/Add-PnPNavigationNode.md index 60dcfa282..aae90f80b 100644 --- a/documentation/Add-PnPNavigationNode.md +++ b/documentation/Add-PnPNavigationNode.md @@ -14,8 +14,16 @@ Adds an item to a navigation element ## SYNTAX +### Default + +```powershell +Add-PnPNavigationNode -Location -Title [-Url ] [-Parent ] [-First] [-External] [-AudienceIds ] [-Connection ] +``` + +### Provide PreviousNode + ```powershell -Add-PnPNavigationNode -Location -Title [-Url ] [-Parent ] [-First] [-External] [-AudienceIds ] [-Connection ] +Add-PnPNavigationNode -Location -Title -PreviousNode [-Url ] [-Parent ] [-External] [-AudienceIds ] [-Connection ] ``` ## DESCRIPTION @@ -65,6 +73,12 @@ Add-PnPNavigationNode -Title "Label" -Location "TopNavigationBar" -Url "http://l Adds a navigation node to the top navigation bar. The navigation node will be created as a label. +### EXAMPLE 7 +```powershell +Add-PnPNavigationNode -Title "Wiki" -Location "QuickLaunch" -Url "wiki/" -PreviousNode 2012 +``` +Adds a navigation node to the quicklaunch. The navigation node will have the title "Wiki" and will link to the Wiki library on the selected Web after the node with the ID 2012. + ## PARAMETERS ### -Connection @@ -100,7 +114,7 @@ Add the new menu item to beginning of the collection ```yaml Type: SwitchParameter -Parameter Sets: (All) +Parameter Sets: Default Required: False Position: Named @@ -125,10 +139,10 @@ Accept wildcard characters: False ``` ### -Parent -The key of the parent. Leave empty to add to the top level +The parent navigation node. Leave empty to add to the top level ```yaml -Type: Int32 +Type: NavigationNodePipeBind Parameter Sets: (All) Required: False @@ -138,6 +152,20 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -PreviousNode +Specifies the navigation node after which the new navigation node will appear in the navigation node collection. + +```yaml +Type: NavigationNodePipeBind +Parameter Sets: Add node after another node + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -Title The title of the node to add diff --git a/src/Commands/Base/PipeBinds/NavigationNodePipeBind.cs b/src/Commands/Base/PipeBinds/NavigationNodePipeBind.cs index 205f811f0..28142b7a1 100644 --- a/src/Commands/Base/PipeBinds/NavigationNodePipeBind.cs +++ b/src/Commands/Base/PipeBinds/NavigationNodePipeBind.cs @@ -1,4 +1,5 @@ using Microsoft.SharePoint.Client; +using PnP.PowerShell.Commands.Branding; using System; using System.Collections.Generic; using System.Linq; @@ -10,6 +11,7 @@ namespace PnP.PowerShell.Commands.Base.PipeBinds public class NavigationNodePipeBind { private int _id; + private NavigationNode _node; public NavigationNodePipeBind(int id) { @@ -18,9 +20,26 @@ public NavigationNodePipeBind(int id) public NavigationNodePipeBind(NavigationNode node) { - _id = node.Id; + _node = node; } - public int Id => _id; + internal NavigationNode GetNavigationNode(Web web) + { + NavigationNode node = null; + if (_node != null) + { + node = _node; + } else { + node = web.Navigation.GetNodeById(_id); + } + + if (node != null) + { + web.Context.Load(node); + web.Context.ExecuteQueryRetry(); + } + + return node; + } } } diff --git a/src/Commands/Navigation/AddNavigationNode.cs b/src/Commands/Navigation/AddNavigationNode.cs index 1e705b503..dd9ed3a43 100644 --- a/src/Commands/Navigation/AddNavigationNode.cs +++ b/src/Commands/Navigation/AddNavigationNode.cs @@ -3,34 +3,55 @@ using System.Management.Automation; using Microsoft.SharePoint.Client; using PnP.Framework.Enums; +using PnP.PowerShell.Commands.Base.PipeBinds; namespace PnP.PowerShell.Commands.Branding { + [Cmdlet(VerbsCommon.Add, "PnPNavigationNode")] [OutputType(typeof(NavigationNode))] public class AddNavigationNode : PnPWebCmdlet { + private const string ParameterSet_Default = "Default"; + private const string ParameterSet_PreviousNode = "Add node after another node"; + + [Parameter(ParameterSetName = ParameterSet_Default)] + [Parameter(ParameterSetName = ParameterSet_PreviousNode)] [Parameter(Mandatory = true)] public NavigationType Location; + [Parameter(ParameterSetName = ParameterSet_Default)] + [Parameter(ParameterSetName = ParameterSet_PreviousNode)] [Parameter(Mandatory = true)] public string Title; + [Parameter(ParameterSetName = ParameterSet_Default)] + [Parameter(ParameterSetName = ParameterSet_PreviousNode)] [Parameter(Mandatory = false)] public string Url; + [Parameter(ParameterSetName = ParameterSet_Default)] + [Parameter(ParameterSetName = ParameterSet_PreviousNode)] [Parameter(Mandatory = false)] - public int? Parent; + public NavigationNodePipeBind Parent; + [Parameter(ParameterSetName = ParameterSet_Default)] [Parameter(Mandatory = false)] public SwitchParameter First; + [Parameter(ParameterSetName = ParameterSet_Default)] + [Parameter(ParameterSetName = ParameterSet_PreviousNode)] [Parameter(Mandatory = false)] public SwitchParameter External; + [Parameter(ParameterSetName = ParameterSet_Default)] + [Parameter(ParameterSetName = ParameterSet_PreviousNode)] [Parameter(Mandatory = false)] public List AudienceIds; + [Parameter(Mandatory = true, ParameterSetName = ParameterSet_PreviousNode)] + public NavigationNodePipeBind PreviousNode; + protected override void ExecuteCmdlet() { if (Url == null) @@ -39,72 +60,70 @@ protected override void ExecuteCmdlet() ClientContext.ExecuteQueryRetry(); Url = CurrentWeb.Url; } - if (Parent.HasValue) + + var navigationNodeCreationInformation = new NavigationNodeCreationInformation { + Title = Title, + Url = Url, + IsExternal = External.IsPresent, + }; + + if (ParameterSpecified(nameof(PreviousNode))) { - var parentNode = CurrentWeb.Navigation.GetNodeById(Parent.Value); - ClientContext.Load(parentNode); - ClientContext.ExecuteQueryRetry(); - var addedNode = parentNode.Children.Add(new NavigationNodeCreationInformation() - { - Title = Title, - Url = Url, - IsExternal = External.IsPresent, - AsLastNode = !First.IsPresent - }); - ClientContext.Load(addedNode); - ClientContext.ExecuteQueryRetry(); - WriteObject(addedNode); + navigationNodeCreationInformation.PreviousNode = PreviousNode.GetNavigationNode(CurrentWeb); + } else + { + navigationNodeCreationInformation.AsLastNode = !First.IsPresent; + } + + NavigationNodeCollection nodeCollection = null; + if (ParameterSpecified(nameof(Parent))) + { + var parentNode = Parent.GetNavigationNode(CurrentWeb); + nodeCollection = parentNode.Children; + CurrentWeb.Context.Load(nodeCollection); + CurrentWeb.Context.ExecuteQueryRetry(); + } + else if (Location == NavigationType.SearchNav) + { + nodeCollection = CurrentWeb.LoadSearchNavigation(); + } + else if (Location == NavigationType.Footer) + { + nodeCollection = CurrentWeb.LoadFooterNavigation(); } else { - NavigationNodeCollection nodeCollection = null; - if (Location == NavigationType.SearchNav) - { - nodeCollection = CurrentWeb.LoadSearchNavigation(); - } - else if (Location == NavigationType.Footer) - { - nodeCollection = CurrentWeb.LoadFooterNavigation(); - } - else - { - nodeCollection = Location == NavigationType.QuickLaunch ? CurrentWeb.Navigation.QuickLaunch : CurrentWeb.Navigation.TopNavigationBar; - ClientContext.Load(nodeCollection); - } - if (nodeCollection != null) + nodeCollection = Location == NavigationType.QuickLaunch ? CurrentWeb.Navigation.QuickLaunch : CurrentWeb.Navigation.TopNavigationBar; + ClientContext.Load(nodeCollection); + } + + if (nodeCollection != null) + { + var addedNode = nodeCollection.Add(navigationNodeCreationInformation); + + if (ParameterSpecified(nameof(AudienceIds))) { - var addedNode = nodeCollection.Add(new NavigationNodeCreationInformation - { - Title = Title, - Url = Url, - IsExternal = External.IsPresent, - AsLastNode = !First.IsPresent - }); - - if (ParameterSpecified(nameof(AudienceIds))) - { - addedNode.AudienceIds = AudienceIds; - addedNode.Update(); - } - - ClientContext.Load(addedNode); - ClientContext.ExecuteQueryRetry(); - - if (Location == NavigationType.QuickLaunch) - { - // Retrieve the menu definition and save it back again. This step is needed to enforce some properties of the menu to be shown, such as the audience targeting. - CurrentWeb.EnsureProperties(w => w.Url); - var menuState = Utilities.REST.RestHelper.GetAsync(Connection.HttpClient, $"{CurrentWeb.Url}/_api/navigation/MenuState", ClientContext, "application/json;odata=nometadata").GetAwaiter().GetResult(); - Utilities.REST.RestHelper.PostAsync(Connection.HttpClient, $"{CurrentWeb.Url}/_api/navigation/SaveMenuState", ClientContext, @"{ ""menuState"": " + menuState + "}", "application/json", "application/json;odata=nometadata").GetAwaiter().GetResult(); - } - - WriteObject(addedNode); + addedNode.AudienceIds = AudienceIds; + addedNode.Update(); } - else + + ClientContext.Load(addedNode); + ClientContext.ExecuteQueryRetry(); + + if (Location == NavigationType.QuickLaunch) { - throw new Exception("Navigation Node Collection is null"); + // Retrieve the menu definition and save it back again. This step is needed to enforce some properties of the menu to be shown, such as the audience targeting. + CurrentWeb.EnsureProperties(w => w.Url); + var menuState = Utilities.REST.RestHelper.GetAsync(Connection.HttpClient, $"{CurrentWeb.Url}/_api/navigation/MenuState", ClientContext, "application/json;odata=nometadata").GetAwaiter().GetResult(); + Utilities.REST.RestHelper.PostAsync(Connection.HttpClient, $"{CurrentWeb.Url}/_api/navigation/SaveMenuState", ClientContext, @"{ ""menuState"": " + menuState + "}", "application/json", "application/json;odata=nometadata").GetAwaiter().GetResult(); } + + WriteObject(addedNode); } + else + { + throw new Exception("Navigation Node Collection is null"); + } } } } diff --git a/src/Commands/Navigation/RemoveNavigationNode.cs b/src/Commands/Navigation/RemoveNavigationNode.cs index ac2a13d96..fd93e02db 100644 --- a/src/Commands/Navigation/RemoveNavigationNode.cs +++ b/src/Commands/Navigation/RemoveNavigationNode.cs @@ -54,7 +54,7 @@ protected override void ExecuteCmdlet() { if (ParameterSetName == ParameterSet_BYID) { - var node = CurrentWeb.Navigation.GetNodeById(Identity.Id); + var node = Identity.GetNavigationNode(CurrentWeb); node.DeleteObject(); ClientContext.ExecuteQueryRetry(); } From 4592196cbb1d86969134138b8784619b223ab8b5 Mon Sep 17 00:00:00 2001 From: Koen Zomers Date: Wed, 12 Apr 2023 00:16:54 +0200 Subject: [PATCH 2/3] Adding changelog entry --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47968dea8..b01a9856d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,14 +13,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Added `DisableDocumentLibraryDefaultLabeling`, `DisableListSync`, `IsEnableAppAuthPopUpEnabled`, `ExpireVersionsAfterDays`, `MajorVersionLimit` and `EnableAutoExpirationVersionTrim`, `OneDriveLoopSharingCapability`, `OneDriveLoopDefaultSharingLinkScope`, `OneDriveLoopDefaultSharingLinkRole`, `CoreLoopSharingCapability`, `CoreLoopDefaultSharingLinkScope`, `CoreLoopDefaultSharingLinkRole` , `DisableVivaConnectionsAnalytics` , `CoreDefaultLinkToExistingAccess`, `HideSyncButtonOnTeamSite` , `CoreBlockGuestsAsSiteAdmin`, `IsWBFluidEnabled`, `IsCollabMeetingNotesFluidEnabled`, `AllowAnonymousMeetingParticipantsToAccessWhiteboards`, `IBImplicitGroupBased`, `ShowOpenInDesktopOptionForSyncedFiles` and `ShowPeoplePickerGroupSuggestionsForIB` parameters to the `Set-PnPTenant` cmdlet. [#2979](https://github.com/pnp/powershell/pull/2979) - Added `-OutFile` to `Invoke-PnPGraphMethod` which allows for the response to be written to a file [#2971](https://github.com/pnp/powershell/pull/2971) - Added `-OutStream` to `Invoke-PnPGraphMethod` which allows for the response to be written to a memory stream [#2976](https://github.com/pnp/powershell/pull/2976) +- Added `-PreviousNode` to `Add-PnPNavigationNode` which allows for adding a navigation node after a specific node [#2940](https://github.com/pnp/powershell/pull/2940) ### Fixed - Fixed issue with `Grant-PnPAzureADAppSitePermission` cmdlet where users are not able to set selected site in the `Sites.Selected` permission. [#2983](https://github.com/pnp/powershell/pull/2983) - Fixed issue with `Get-PnPList` cmdlet not working with site-relative URL as identity. [#3005](https://github.com/pnp/powershell/pull/3005) +- Fixed issue with `Add-PnPNavigationNode` cmdlet where the target audience would not correctly be set when creating a node as a child of a parent node [#2940](https://github.com/pnp/powershell/pull/2940) ### Contributors +- [reusto] - [dhiabedoui] - Koen Zomers [koenzomers] From 25f4c2d855880f1b417328ed1275d91dce55e9fd Mon Sep 17 00:00:00 2001 From: Koen Zomers Date: Wed, 12 Apr 2023 00:17:20 +0200 Subject: [PATCH 3/3] Adding default param instruction, changing exception to be a bit less technical to end users --- src/Commands/Navigation/AddNavigationNode.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Commands/Navigation/AddNavigationNode.cs b/src/Commands/Navigation/AddNavigationNode.cs index dd9ed3a43..160bfc1ba 100644 --- a/src/Commands/Navigation/AddNavigationNode.cs +++ b/src/Commands/Navigation/AddNavigationNode.cs @@ -7,8 +7,7 @@ namespace PnP.PowerShell.Commands.Branding { - - [Cmdlet(VerbsCommon.Add, "PnPNavigationNode")] + [Cmdlet(VerbsCommon.Add, "PnPNavigationNode", DefaultParameterSetName = ParameterSet_Default)] [OutputType(typeof(NavigationNode))] public class AddNavigationNode : PnPWebCmdlet { @@ -122,7 +121,7 @@ protected override void ExecuteCmdlet() } else { - throw new Exception("Navigation Node Collection is null"); + throw new Exception("Unable to define Navigation Node collection to add the node to"); } } }