From 799859986efe27121f40cf285ad95cc66fb8c2bf Mon Sep 17 00:00:00 2001 From: Koen Zomers Date: Thu, 3 Oct 2024 19:53:56 +0200 Subject: [PATCH] Added implementeion for Get-PnPSearchExternalItem --- documentation/Get-PnPSearchExternalItem.md | 105 ++++++++++++++++++ resources/PnP.PowerShell.Format.ps1xml | 30 +++++ src/Commands/Base/InvokeQuery.cs | 1 - .../Model/Graph/MicrosoftSearch/SearchHit.cs | 40 +++++++ .../MicrosoftSearch/SearchHitsContainer.cs | 17 +++ .../Graph/MicrosoftSearch/SearchRequest.cs | 28 +++++ .../MicrosoftSearch/SearchRequestQuery.cs | 15 +++ .../Graph/MicrosoftSearch/SearchRequests.cs | 16 +++ .../Graph/MicrosoftSearch/SearchResult.cs | 17 +++ .../MicrosoftSearch/SearchResultResource.cs | 17 +++ .../Search/GetSearchExternalConnection.cs | 4 +- src/Commands/Search/GetSearchExternalItem.cs | 80 +++++++++++++ 12 files changed, 368 insertions(+), 2 deletions(-) create mode 100644 documentation/Get-PnPSearchExternalItem.md create mode 100644 src/Commands/Model/Graph/MicrosoftSearch/SearchHit.cs create mode 100644 src/Commands/Model/Graph/MicrosoftSearch/SearchHitsContainer.cs create mode 100644 src/Commands/Model/Graph/MicrosoftSearch/SearchRequest.cs create mode 100644 src/Commands/Model/Graph/MicrosoftSearch/SearchRequestQuery.cs create mode 100644 src/Commands/Model/Graph/MicrosoftSearch/SearchRequests.cs create mode 100644 src/Commands/Model/Graph/MicrosoftSearch/SearchResult.cs create mode 100644 src/Commands/Model/Graph/MicrosoftSearch/SearchResultResource.cs create mode 100644 src/Commands/Search/GetSearchExternalItem.cs diff --git a/documentation/Get-PnPSearchExternalItem.md b/documentation/Get-PnPSearchExternalItem.md new file mode 100644 index 000000000..e8310eace --- /dev/null +++ b/documentation/Get-PnPSearchExternalItem.md @@ -0,0 +1,105 @@ +--- +Module Name: PnP.PowerShell +schema: 2.0.0 +applicable: SharePoint Online +online version: https://pnp.github.io/powershell/cmdlets/Get-PnPSearchExternalItem.html +external help file: PnP.PowerShell.dll-Help.xml +title: Get-PnPSearchExternalItem +--- + +# Get-PnPSearchExternalItem + +## SYNOPSIS + +**Required Permissions** + + * Microsoft Graph API: One of ExternalItem.ReadWrite.OwnedBy, ExternalItem.Read.All, ExternalItem.ReadWrite.All under a delegated context. Application context is not supported. + +Returns the external items indexed for a specific connector in Microsoft Search + +## SYNTAX + +```powershell +Get-PnPSearchExternalItem -ConnectionId [-Identity ] [-Verbose] [-Connection ] +``` + +## DESCRIPTION + +This cmdlet can be used to retrieve a list of indexed external items for a specific Microsoft Search external connector. The cmdlet will return all indexed external items for the specified connector. If you want to retrieve a specific external item, you can use the Identity parameter to specify the unique identifier of the external item. It uses a Microsoft Graph query in the background to retrieve the external items. This is why it will be unable to return the Access Control Lists (ACLs) information in the external items and the properties to contain more properties than you ingested yourself. + +It is only possible to run this cmdlet under a delegated context, application context is not supported by the Microsoft Graph search API endpoint for this type of query. + +## EXAMPLES + +### EXAMPLE 1 +```powershell +Get-PnPSearchExternalItem -ConnectionId "pnppowershell" -ItemId "12345" +``` + +This will return the external item with the unique identifier "12345" for the custom connector with the Connection ID "pnppowershell". + +### EXAMPLE 2 +```powershell +Get-PnPSearchExternalItem -ConnectionId "pnppowershell" +``` + +This will return all external items for the custom connector with the Connection ID "pnppowershell". + +## PARAMETERS + +### -Connection +Optional connection to be used by the cmdlet. Retrieve the value for this parameter by either specifying -ReturnConnection on Connect-PnPOnline or by executing Get-PnPConnection. + +```yaml +Type: PnPConnection +Parameter Sets: (All) +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Identity +Unique identifier of the external item in Microsoft Search. You can provide any identifier you want to retrieve or check for a specific item in the index. If you omit it, all external items for the specified connector will be returned. + +```yaml +Type: String +Parameter Sets: (All) +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ConnectionId +The Connection ID or connection instance of the custom connector to use. This is the ID that was entered when registering the custom connector and will indicate for which custom connector the external items will be returned from the Microsoft Search index. + +```yaml +Type: SearchExternalConnectionPipeBind +Parameter Sets: (All) +Required: True +Default value: None +Accept pipeline input: True +Accept wildcard characters: False +``` + +### -Verbose +When provided, additional debug statements will be shown while executing the cmdlet. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +## RELATED LINKS + +[Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) +[Microsoft Graph documentation](https://learn.microsoft.com/graph/search-concept-custom-types#example-1-retrieve-items-using-azure-sql-built-in-connector) \ No newline at end of file diff --git a/resources/PnP.PowerShell.Format.ps1xml b/resources/PnP.PowerShell.Format.ps1xml index 314b9f3b9..29386854a 100644 --- a/resources/PnP.PowerShell.Format.ps1xml +++ b/resources/PnP.PowerShell.Format.ps1xml @@ -3016,5 +3016,35 @@ + + ExternalItem + + PnP.PowerShell.Commands.Model.Graph.MicrosoftSearch.ExternalItem + + + + + + left + + + + left + + + + + + + Id + + + $_.Content.Value + + + + + + \ No newline at end of file diff --git a/src/Commands/Base/InvokeQuery.cs b/src/Commands/Base/InvokeQuery.cs index 7d5751595..413f03eff 100644 --- a/src/Commands/Base/InvokeQuery.cs +++ b/src/Commands/Base/InvokeQuery.cs @@ -1,7 +1,6 @@ using System.Management.Automation; using Microsoft.SharePoint.Client; - namespace PnP.PowerShell.Commands.Base { [Cmdlet(VerbsLifecycle.Invoke, "PnPQuery")] diff --git a/src/Commands/Model/Graph/MicrosoftSearch/SearchHit.cs b/src/Commands/Model/Graph/MicrosoftSearch/SearchHit.cs new file mode 100644 index 000000000..21d37cea7 --- /dev/null +++ b/src/Commands/Model/Graph/MicrosoftSearch/SearchHit.cs @@ -0,0 +1,40 @@ +using System.Text.Json.Serialization; + +namespace PnP.PowerShell.Commands.Model.Graph.MicrosoftSearch; + +/// +/// Defines a hit in a search result from Microsoft Graph +/// +/// +public class SearchHit +{ + /// + /// Unique identifier of the search result + /// + [JsonPropertyName("hitId")] + public string Id { get; set; } + + /// + /// Name of the source of the content + /// + [JsonPropertyName("contentSource")] + public string ContentSource { get; set; } + + /// + /// The rank of the search result + /// + [JsonPropertyName("rank")] + public short Rank { get; set; } + + /// + /// A summary of the search result + /// + [JsonPropertyName("summary")] + public string Summary { get; set; } + + /// + /// The resource properties of the search result + /// + [JsonPropertyName("resource")] + public SearchResultResource Resource { get; set; } +} \ No newline at end of file diff --git a/src/Commands/Model/Graph/MicrosoftSearch/SearchHitsContainer.cs b/src/Commands/Model/Graph/MicrosoftSearch/SearchHitsContainer.cs new file mode 100644 index 000000000..71b034226 --- /dev/null +++ b/src/Commands/Model/Graph/MicrosoftSearch/SearchHitsContainer.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace PnP.PowerShell.Commands.Model.Graph.MicrosoftSearch; + +/// +/// Defines a hitcontainer in a search result from Microsoft Graph +/// +/// +public class SearchHitsContainer +{ + /// + /// Collection with search hits + /// + [JsonPropertyName("hits")] + public List Hits { get; set; } +} \ No newline at end of file diff --git a/src/Commands/Model/Graph/MicrosoftSearch/SearchRequest.cs b/src/Commands/Model/Graph/MicrosoftSearch/SearchRequest.cs new file mode 100644 index 000000000..07e526e65 --- /dev/null +++ b/src/Commands/Model/Graph/MicrosoftSearch/SearchRequest.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace PnP.PowerShell.Commands.Model.Graph.MicrosoftSearch; + +/// +/// Defines the body for a single search request to Microsoft Graph +/// +public class SearchRequest +{ + /// + /// The types of entities to query, i.e. externalItem + /// + [JsonPropertyName("entityTypes")] + public List EntityTypes { get; set; } + + /// + /// The names of the content sources to query, i.e. /external/connections/ + /// + [JsonPropertyName("contentSources")] + public List ContentSources { get; set; } + + /// + /// The search query to execute + /// + [JsonPropertyName("query")] + public SearchRequestQuery Query { get; set; } +} \ No newline at end of file diff --git a/src/Commands/Model/Graph/MicrosoftSearch/SearchRequestQuery.cs b/src/Commands/Model/Graph/MicrosoftSearch/SearchRequestQuery.cs new file mode 100644 index 000000000..440f001dd --- /dev/null +++ b/src/Commands/Model/Graph/MicrosoftSearch/SearchRequestQuery.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace PnP.PowerShell.Commands.Model.Graph.MicrosoftSearch; + +/// +/// Defines the query for request to Microsoft Graph +/// +public class SearchRequestQuery +{ + /// + /// The search query to execute + /// + [JsonPropertyName("queryString")] + public string QueryString { get; set; } +} \ No newline at end of file diff --git a/src/Commands/Model/Graph/MicrosoftSearch/SearchRequests.cs b/src/Commands/Model/Graph/MicrosoftSearch/SearchRequests.cs new file mode 100644 index 000000000..e4ea0dfb9 --- /dev/null +++ b/src/Commands/Model/Graph/MicrosoftSearch/SearchRequests.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace PnP.PowerShell.Commands.Model.Graph.MicrosoftSearch; + +/// +/// Defines the body for a collection of search requests to Microsoft Graph +/// +public class SearchRequests +{ + /// + /// Collection of search request items + /// + [JsonPropertyName("requests")] + public List Requests { get; set; } +} \ No newline at end of file diff --git a/src/Commands/Model/Graph/MicrosoftSearch/SearchResult.cs b/src/Commands/Model/Graph/MicrosoftSearch/SearchResult.cs new file mode 100644 index 000000000..2c2881d0e --- /dev/null +++ b/src/Commands/Model/Graph/MicrosoftSearch/SearchResult.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace PnP.PowerShell.Commands.Model.Graph.MicrosoftSearch; + +/// +/// Defines a search result from Microsoft Graph +/// +/// +public class SearchResult +{ + /// + /// Unique identifier of the search result + /// + [JsonPropertyName("hitsContainers")] + public List HitsContainers { get; set; } +} \ No newline at end of file diff --git a/src/Commands/Model/Graph/MicrosoftSearch/SearchResultResource.cs b/src/Commands/Model/Graph/MicrosoftSearch/SearchResultResource.cs new file mode 100644 index 000000000..ea692ecc3 --- /dev/null +++ b/src/Commands/Model/Graph/MicrosoftSearch/SearchResultResource.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace PnP.PowerShell.Commands.Model.Graph.MicrosoftSearch; + +/// +/// Defines the resources within a search result from Microsoft Graph +/// +/// +public class SearchResultResource +{ + /// + /// The properties of the search result + /// + [JsonPropertyName("properties")] + public Dictionary Properties { get; set; } +} \ No newline at end of file diff --git a/src/Commands/Search/GetSearchExternalConnection.cs b/src/Commands/Search/GetSearchExternalConnection.cs index 693f7f235..7c5caae22 100644 --- a/src/Commands/Search/GetSearchExternalConnection.cs +++ b/src/Commands/Search/GetSearchExternalConnection.cs @@ -6,7 +6,9 @@ namespace PnP.PowerShell.Commands.Search { [Cmdlet(VerbsCommon.Get, "PnPSearchExternalConnection")] - [RequiredApiApplicationPermissions("graph/ExternalConnection.ReadWrite.OwnedBy")] + [RequiredApiDelegatedOrApplicationPermissions("graph/ExternalConnection.ReadWrite.OwnedBy")] + [RequiredApiDelegatedOrApplicationPermissions("graph/ExternalConnection.Read.All")] + [RequiredApiDelegatedOrApplicationPermissions("graph/ExternalConnection.ReadWrite.OwnedBy")] [OutputType(typeof(IEnumerable))] [OutputType(typeof(Model.Graph.MicrosoftSearch.ExternalConnection))] public class GetSearchExternalConnection : PnPGraphCmdlet diff --git a/src/Commands/Search/GetSearchExternalItem.cs b/src/Commands/Search/GetSearchExternalItem.cs new file mode 100644 index 000000000..3631fa8a8 --- /dev/null +++ b/src/Commands/Search/GetSearchExternalItem.cs @@ -0,0 +1,80 @@ +using System.Management.Automation; +using PnP.PowerShell.Commands.Base; +using PnP.PowerShell.Commands.Base.PipeBinds; +using PnP.PowerShell.Commands.Attributes; +using PnP.PowerShell.Commands.Utilities.REST; +using System.Net.Http; +using System.Text.Json; +using System.Net.Http.Headers; +using System.Linq; + +namespace PnP.PowerShell.Commands.Search +{ + [Cmdlet(VerbsCommon.Get, "PnPSearchExternalItem")] + [RequiredApiDelegatedPermissions("graph/ExternalItem.Read.All")] + [RequiredApiDelegatedPermissions("graph/ExternalItem.ReadWrite.All")] + [RequiredApiDelegatedPermissions("graph/ExternalItem.ReadWrite.OwnedBy")] + [ApiNotAvailableUnderApplicationPermissions] + [OutputType(typeof(Model.Graph.MicrosoftSearch.ExternalItem[]))] + public class GetSearchExternalItem : PnPGraphCmdlet + { + [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)] + public SearchExternalConnectionPipeBind ConnectionId; + + [Parameter(Mandatory = false)] + [ValidateLength(1,128)] + public string Identity; + + protected override void ExecuteCmdlet() + { + var externalConnection = ConnectionId.GetExternalConnection(this, Connection, AccessToken); + + var searchQuery = new Model.Graph.MicrosoftSearch.SearchRequests + { + Requests = + [ + new () + { + EntityTypes = + [ + "externalItem" + ], + ContentSources = + [ + $"/external/connections/{externalConnection.Id}" + ], + Query = new Model.Graph.MicrosoftSearch.SearchRequestQuery + { + QueryString = ParameterSpecified(nameof(Identity)) ? $"fileID:{Identity}" : "*" + } + } + ] + }; + + var httpContent = new StringContent(JsonSerializer.Serialize(searchQuery), System.Text.Encoding.UTF8); + httpContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); + + // Execute the search query to discover the external items + var searchResults = GraphHelper.Post>(this, Connection, "v1.0/search/query", httpContent, AccessToken); + + var hits = searchResults.Items.FirstOrDefault().HitsContainers.FirstOrDefault().Hits; + + if(hits == null || hits.Count == 0) + { + WriteVerbose($"No external items found{(ParameterSpecified(nameof(Identity)) ? $" with the identity '{Identity}'" : "")} on external connection '{externalConnection.Id}'"); + return; + } + + WriteVerbose($"Found {hits.Count} external item{(hits.Count != 1 ? "s" : "")}{(ParameterSpecified(nameof(Identity)) ? $" with the identity '{Identity}'" : "")} on external connection '{externalConnection.Id}'"); + + var externalItems = hits.Select(s => new Model.Graph.MicrosoftSearch.ExternalItem { + Id = s.Resource.Properties["fileID"].ToString()[(s.Resource.Properties["fileID"].ToString().LastIndexOf(',') + 1)..], + Acls = null, + Properties = new System.Collections.Hashtable(s.Resource.Properties), + Content = new Model.Graph.MicrosoftSearch.ExternalItemContent { Type = Enums.SearchExternalItemContentType.Html, Value = s.Summary } + }).ToArray(); + + WriteObject(externalItems, true); + } + } +} \ No newline at end of file