Add Microsoft Search vertical, result type, and site connection cmdlets#5242
Add Microsoft Search vertical, result type, and site connection cmdlets#5242
Conversation
Standardize Required Permissions formatting across all four SearchExternalConnection docs: remove "One of", fix tab characters, use "or" separator. Add cross-reference to Get-PnPSearchSiteConnection from Get-PnPSearchExternalConnection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds PnPGcsCmdlet base class with GCS API helpers (retry logic, URL builders, shared validation, site ID caching via EnsureProperties), token handler support for gcs.office.com, SearchVertical/SearchResultType/ SearchSiteConnection models, enums, format views, and a known SharePoint managed properties reference for validation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds Get/New/Set/Remove-PnPSearchVertical for managing custom search verticals with support for SharePoint and external connector content sources. Adds Set-PnPSearchVerticalOrder for reordering custom verticals via delete/recreate. Adds Get-PnPSearchSiteConnection for retrieving available connector site connections. Remove cmdlets support -Force/-WhatIf/-Confirm. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds Get/New/Set/Remove-PnPSearchResultType for managing search result types with connector and SharePoint content source support. Adds New-PnPSearchResultTypeRule helper for building rule objects. Includes -Validate switch for schema validation against connector properties, SharePoint managed property checking, and Adaptive Card v1.3 validation. Set supports -Priority with automatic resequencing. Remove cmdlets support -Force/-WhatIf/-Confirm. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds help docs for all 11 new search cmdlets: Get/New/Set/Remove vertical, Set vertical order, Get site connection, Get/New/Set/Remove result type, and New result type rule. Includes prerequisites, examples, parameter descriptions, and related links. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add GCS API permission prerequisites and token handler references to Get-PnPFlow, Get-PnPPowerApp, Get-PnPPowerPlatformEnvironment, and Get-PnPUnifiedAuditLog docs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The GCS API is an internal Microsoft API that is not publicly documented or officially supported. Added a one-time WriteWarning in PnPGcsCmdlet base class and a WARNING note in all 10 GCS cmdlet docs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new Microsoft Search administration surface to PnP PowerShell by introducing cmdlets that manage Search verticals, modern result types, and site connections through the internal Graph Connector Service (GCS) API (gcs.office.com), plus shared infrastructure, models, formatting, and documentation updates.
Changes:
- Introduces 11 new cmdlets for search verticals, result types, and site connections using a new
PnPGcsCmdletbase class with retry/validation helpers. - Adds new Microsoft Search model types/enums and token/audience support for the GCS resource.
- Adds PowerShell formatting views and comprehensive cmdlet markdown documentation (plus small doc touch-ups to existing cmdlets).
Reviewed changes
Copilot reviewed 41 out of 41 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Commands/Search/SetSearchVerticalOrder.cs | Implements reorder cmdlet using delete/recreate strategy with verification loops |
| src/Commands/Search/SetSearchVertical.cs | Implements update cmdlet for verticals (properties vs payload parameter sets) |
| src/Commands/Search/SetSearchResultType.cs | Implements update cmdlet for modern result types incl. priority resequencing |
| src/Commands/Search/RemoveSearchVertical.cs | Implements remove cmdlet for verticals with -Force and ShouldProcess |
| src/Commands/Search/RemoveSearchResultType.cs | Implements remove cmdlet for result types with -Force and ShouldProcess |
| src/Commands/Search/NewSearchVertical.cs | Implements create cmdlet for custom verticals incl. content source resolution |
| src/Commands/Search/NewSearchResultTypeRule.cs | Adds helper cmdlet to construct result-type rule objects |
| src/Commands/Search/NewSearchResultType.cs | Implements create cmdlet for modern result types incl. optional validation |
| src/Commands/Search/GetSearchVertical.cs | Implements get cmdlet for verticals (single or list) |
| src/Commands/Search/GetSearchSiteConnection.cs | Implements get cmdlet for site connections (single or list) |
| src/Commands/Search/GetSearchResultType.cs | Implements get cmdlet for result types (single by id/name or list) |
| src/Commands/Model/Graph/MicrosoftSearch/SearchVertical.cs | Adds vertical payload/collection models w/ extension-data preservation |
| src/Commands/Model/Graph/MicrosoftSearch/SearchSiteConnection.cs | Adds site connection + schema/property wrapper models |
| src/Commands/Model/Graph/MicrosoftSearch/SearchResultType.cs | Adds result type payload/collection models w/ extension-data preservation |
| src/Commands/Model/Graph/MicrosoftSearch/KnownSharePointManagedProperties.cs | Adds managed property allowlist used for validation warnings |
| src/Commands/Enums/SearchVerticalScope.cs | Adds scope enum (Site vs Organization) used across new cmdlets |
| src/Commands/Enums/SearchResultTypeRuleOperatorType.cs | Adds operator enum for rule construction helper cmdlet |
| src/Commands/Enums/ResourceTypeName.cs | Adds Gcs resource type for access token retrieval/mapping |
| src/Commands/Base/TokenHandler.cs | Maps gcs.office.com audience to new ResourceTypeName.Gcs |
| src/Commands/Base/PnPGcsCmdlet.cs | New base class: disclaimer, headers, URL builders, retry and validation utilities |
| src/Commands/Base/GetAccessToken.cs | Allows Get-PnPAccessToken -ResourceTypeName Gcs |
| resources/PnP.PowerShell.Format.ps1xml | Adds table views for SearchVertical and SearchResultType |
| documentation/Set-PnPSearchVerticalOrder.md | New cmdlet documentation (reorder behavior + warnings/prereqs) |
| documentation/Set-PnPSearchVertical.md | New cmdlet documentation (update verticals) |
| documentation/Set-PnPSearchResultType.md | New cmdlet documentation (update result types) |
| documentation/Set-PnPSearchExternalConnection.md | Minor formatting/wording fix for permissions section |
| documentation/Remove-PnPSearchVertical.md | New cmdlet documentation (remove verticals) |
| documentation/Remove-PnPSearchResultType.md | New cmdlet documentation (remove result types) |
| documentation/Remove-PnPSearchExternalConnection.md | Minor formatting/wording fix for permissions section |
| documentation/New-PnPSearchVertical.md | New cmdlet documentation (create verticals) |
| documentation/New-PnPSearchResultTypeRule.md | New cmdlet documentation (rule helper) |
| documentation/New-PnPSearchResultType.md | New cmdlet documentation (create result types) |
| documentation/New-PnPSearchExternalConnection.md | Minor formatting/wording fix for permissions section |
| documentation/Get-PnPUnifiedAuditLog.md | Adds prerequisites section with permission/app id guidance |
| documentation/Get-PnPSearchVertical.md | New cmdlet documentation (get verticals) |
| documentation/Get-PnPSearchSiteConnection.md | New cmdlet documentation (get site connections) |
| documentation/Get-PnPSearchResultType.md | New cmdlet documentation (get result types) |
| documentation/Get-PnPSearchExternalConnection.md | Minor permissions formatting + adds related link to site connections |
| documentation/Get-PnPPowerPlatformEnvironment.md | Adds prerequisites section (Azure Service Management API permission) |
| documentation/Get-PnPPowerApp.md | Adds prerequisites section (Azure Service Management API permission) |
| documentation/Get-PnPFlow.md | Adds prerequisites section (Azure Service Management API + related notes) |
| try | ||
| { | ||
| GcsRequestHelper.Get<SearchVertical>(url, additionalHeaders: headers); | ||
| // Still exists - keep waiting | ||
| WriteVerbose($"Vertical '{logicalId}' still exists, waiting... ({i + 1}/{MaxVerifyAttempts})"); | ||
| } | ||
| catch | ||
| { | ||
| // Error means the vertical is gone | ||
| WriteVerbose($"Verified vertical '{logicalId}' deleted"); | ||
| return; | ||
| } |
There was a problem hiding this comment.
WaitUntilDeleted() interprets any exception from the GET as confirmation of deletion. This can incorrectly “verify” deletion on transient server errors. Prefer checking for a specific NotFound/404 response (e.g., GraphException with StatusCode == NotFound) and continue waiting/retrying for other error codes.
| /// <summary> | ||
| /// Static cache of site IDs keyed by connection URL, persists across cmdlet invocations within the same session. | ||
| /// </summary> | ||
| private static readonly Dictionary<string, Guid> _siteIdCache = new Dictionary<string, Guid>(StringComparer.OrdinalIgnoreCase); | ||
|
|
||
| /// <summary> | ||
| /// Tracks whether the unsupported API disclaimer has been shown in this session. | ||
| /// </summary> | ||
| private static bool _apiDisclaimerShown; | ||
|
|
There was a problem hiding this comment.
_siteIdCache is a static Dictionary accessed without synchronization. If cmdlets run concurrently across runspaces, this can lead to race conditions or Dictionary corruption. Consider using ConcurrentDictionary or locking around reads/writes (same applies to _apiDisclaimerShown if it can be accessed concurrently).
| if (ParameterSpecified(nameof(Priority))) | ||
| { | ||
| var desiredPriority = Math.Max(1, Priority.Value); | ||
| ResequenceResultTypePriorities(logicalId, desiredPriority, siteId, headers); | ||
| payload.Priority = desiredPriority; | ||
| } |
There was a problem hiding this comment.
desiredPriority is only clamped to a minimum of 1, but not to the maximum valid position. ResequenceResultTypePriorities() inserts at min(desiredPriority-1, others.Count) (end of list), yet the target payload is still set to the original (possibly too-large) priority, which can leave gaps/out-of-range priorities. Consider clamping desiredPriority to the list size (or using insertIdx + 1) before setting payload.Priority.
| DisplayTemplate = DisplayTemplate ?? GetDefaultDisplayTemplate(), | ||
| DisplayProperties = displayProps, | ||
| DisplaySampleData = "", | ||
| LastModifiedBy = "" | ||
| }; |
There was a problem hiding this comment.
LastModifiedBy is described/treated elsewhere as a server-only field to omit on write (set to null). Here it’s initialized to an empty string, which will still be serialized and sent to the API. Consider setting it to null (or not setting it at all) so it’s excluded from the request body.
| DeleteWithRetry(verticalUrl, headers, verifySuccess: () => | ||
| { | ||
| // Check if the vertical is actually gone despite the 500 error | ||
| try | ||
| { | ||
| GcsRequestHelper.Get<SearchVertical>(verticalUrl, additionalHeaders: headers); | ||
| return false; // Still exists | ||
| } | ||
| catch | ||
| { | ||
| return true; // Gone — delete succeeded | ||
| } |
There was a problem hiding this comment.
The delete verifySuccess callback treats any exception from the follow-up GET as “gone”. This can produce false positives (e.g., transient 500/timeout/permission issues) and the cmdlet may proceed while the vertical still exists. Consider only treating a 404/NotFound as deleted and rethrowing/returning false for other failures so retries/verification behave correctly.
| return new Dictionary<string, string> | ||
| { | ||
| { "x-siteurl", Connection.Url } |
There was a problem hiding this comment.
GetGcsHeaders() unconditionally adds x-siteurl with Connection.Url. PnPConnection.Url can be null for some connection types, which would lead to sending an invalid header (or failing later in a less obvious way). Consider validating Connection?.Url and throwing a clear error if no site URL is available (especially for org-scope cmdlets that otherwise wouldn’t call GetCurrentSiteId()).
| return new Dictionary<string, string> | |
| { | |
| { "x-siteurl", Connection.Url } | |
| var url = Connection?.Url; | |
| if (string.IsNullOrEmpty(url)) | |
| { | |
| throw new PSInvalidOperationException("No site URL available on the current connection. Connect to a specific SharePoint site before invoking this cmdlet."); | |
| } | |
| return new Dictionary<string, string> | |
| { | |
| { "x-siteurl", url } |
| if (!Force && !ShouldProcess($"Search vertical '{Identity}' at {Scope} scope", "Remove")) | ||
| return; |
There was a problem hiding this comment.
-Force bypasses ShouldProcess(), which means -WhatIf/-Confirm are ignored when -Force is used. With SupportsShouldProcess=true, the cmdlet should still call ShouldProcess even under -Force (use Force only to skip confirmation prompts, not WhatIf semantics).
| if (!Force && !ShouldProcess($"Search result type '{Identity}' at {Scope} scope", "Remove")) | ||
| return; |
There was a problem hiding this comment.
-Force currently skips calling ShouldProcess(), so -WhatIf and -Confirm will not be honored when -Force is present. Keep ShouldProcess in the execution path and use -Force only to bypass additional confirmation (e.g., ShouldContinue) so WhatIf remains safe.
| if (ParameterSpecified(nameof(IncludeConnectorResults))) | ||
| payload.IncludeConnectorResults = IncludeConnectorResults; | ||
|
|
||
| // Clear server-only fields that shouldn't be sent in the PUT | ||
| payload.LastModifiedBy = null; | ||
| } |
There was a problem hiding this comment.
IncludeConnectorResults is applied unconditionally when specified. This conflicts with the stated behavior/documentation that it only applies to built-in verticals (SITEALL/ALL) and should be ignored or rejected for custom verticals. Consider checking the vertical type (from the GET payload) and either warn+ignore or throw when IncludeConnectorResults is bound for a custom vertical, so runtime behavior matches the docs/PR description.
| // 6. Check if order is actually different | ||
| var currentOrder = customVerticals.Keys.ToList(); | ||
| var alreadyInOrder = currentOrder.SequenceEqual(Identity, StringComparer.OrdinalIgnoreCase); |
There was a problem hiding this comment.
currentOrder is derived from customVerticals.Keys, but customVerticals is a Dictionary and its key enumeration order is not a guaranteed representation of the API’s returned order. Since the reorder optimization depends on the existing order, build currentOrder from the original allVerticals sequence (filtered to custom verticals) instead of dictionary keys.
Before creating a pull request, make sure that you have read the contribution file located at
https://github.com/pnp/powerShell/blob/dev/CONTRIBUTING.md
Type
Related Issues?
N/A - New feature contribution
What is in this Pull Request ?
Adds a full set of cmdlets for managing Microsoft Search verticals, result types, and site connections via the Graph Connector Service (GCS) API at gcs.office.com.
API & Permissions
These cmdlets use the Graph Connector Service (GCS) API, a first-party Microsoft API registered under app ID
56c1da01-2129-48f7-9355-af6d59d42766. The required delegated permission is:ExternalConnection.ReadWrite.All(scope IDd44774bd-e26c-43b1-996d-51bb90a9078e) from the GCS APIThis is NOT the standard Microsoft Graph
ExternalConnection.ReadWrite.Allpermission — it's from the GCS-specific API registration. Users must add this permission to their Entra app registration:New Cmdlets
Search Verticals:
Get-PnPSearchVertical– Retrieve search verticals (site or organization scope)New-PnPSearchVertical– Create custom search verticals with SharePoint or external connector content sourcesSet-PnPSearchVertical– Update vertical properties (display name, enabled state, query template, connector results)Remove-PnPSearchVertical– Remove search verticals with-Force/-WhatIf/-ConfirmsupportSet-PnPSearchVerticalOrder– Reorder custom verticals (delete-and-recreate strategy with verification)Search Result Types:
Get-PnPSearchResultType– Retrieve result types by logical ID or nameNew-PnPSearchResultType– Create result types with rules, Adaptive Card v1.3 templates, and display propertiesSet-PnPSearchResultType– Update result types including priority resequencingRemove-PnPSearchResultType– Remove result types with-Force/-WhatIf/-ConfirmsupportNew-PnPSearchResultTypeRule– Create rule objects for result type matching (7 operator types)Site Connections:
Get-PnPSearchSiteConnection– Retrieve external connector site connections available for search verticalsInfrastructure
PnPGcsCmdletbase class with shared GCS API helpers: URL builders, retry logic with exponential backoff,PostAndGet<T>pattern (handles GCS empty-body responses), connector property validation, Adaptive Card version validation, site ID caching per connection URLWriteWarninginPnPGcsCmdlet.BeginProcessing()ResourceTypeName.GraphConnectorServiceandTokenHandlerSearchVertical,SearchResultType,SearchSiteConnectionwith full JSON serializationSearchVerticalScope(Site/Organization),SearchResultTypeRuleOperatorType(7 operators)KnownSharePointManagedProperties– 430+ properties for-Validatevalidation against SharePoint schemaPnP.PowerShell.Format.ps1xmlfor table output of verticals, result types, and connectionsKey Design Decisions
-Enabledisbool(notSwitchParameter) to allow$true/$falsetoggle for enable/disable-Payloadparameter set on New/Set cmdlets for full control (clone-and-modify workflows)-Validateswitch on result type cmdlets fetches connector schema and validates rule/display properties-IncludeConnectorResultsonly applies to built-in verticals (SITEALL/ALL); warns and ignores for custom verticalsSupportsShouldProcesson Remove cmdlets with-Forceto skip confirmationDocumentation
Full documentation for all 11 new cmdlets following existing PnP PowerShell conventions, including prerequisites, multiple examples, and parameter descriptions. Each GCS cmdlet doc includes an unsupported API warning and the specific permission/app registration instructions. Also includes minor documentation updates for existing Power Platform and Audit Log cmdlets.