Public/New-CopilotSearch.ps1
|
function New-CopilotSearch { <# .SYNOPSIS Perform hybrid search across OneDrive for work or school content using natural language queries. .DESCRIPTION The Microsoft 365 Copilot Search API performs hybrid (semantic and lexical) search across OneDrive for work or school content by using natural language queries with contextual understanding. Discover relevant documents and files that you have access to, while respecting the defined access controls within the organization. The Search API supports up to 20 batch requests per call. .PARAMETER Query Natural language query to search for relevant files. This parameter has a limit of 1,500 characters. Your query should use natural language with contextual understanding for best results. .PARAMETER PageSize Number of results to return per page. Must be between 1 and 100. Default: 25. .PARAMETER FilterExpression Keyword Query Language (KQL) expression to filter OneDrive content. Use path expressions to scope the search to specific locations. Supported properties include: Author, FileExtension, Filename, FileType, LastModifiedTime, ModifiedBy, Path, and Title. .PARAMETER ResourceMetadata A list of metadata field names to be returned for each search result in the response. Common values include: 'title', 'author'. .PARAMETER BatchQueries Array of query strings to execute as a batch request. Allows up to 20 queries in a single batch. When using batch queries, other parameters (PageSize, FilterExpression, ResourceMetadata) will apply to all queries in the batch. .EXAMPLE New-CopilotSearch -Query "How to setup corporate VPN?" Performs a basic hybrid search for VPN setup information across OneDrive content. .EXAMPLE New-CopilotSearch -Query "quarterly budget analysis" -PageSize 10 Searches for budget analysis documents and returns up to 10 results. .EXAMPLE New-CopilotSearch -Query "quarterly budget analysis" -FilterExpression 'path:"https://contoso-my.sharepoint.com/personal/megan_contoso_com/Documents/Finance/"' -ResourceMetadata @("title", "author") Searches within a specific OneDrive path and includes title and author metadata in results. .EXAMPLE New-CopilotSearch -Query "project timeline milestones" -FilterExpression 'path:"https://contoso-my.sharepoint.com/personal/john_contoso_com/Documents/Projects/"' -ResourceMetadata @("title", "author") -PageSize 5 Searches for project documents within a specific path with metadata, limiting to 5 results. .EXAMPLE New-CopilotSearch -Query "quarterly budget analysis" -FilterExpression 'path:"https://contoso-my.sharepoint.com/personal/megan_contoso_com/Documents/Finance/" OR path:"https://contoso-my.sharepoint.com/personal/megan_contoso_com/Documents/Budget"' -ResourceMetadata @("title", "author") Searches across multiple OneDrive paths using OR logic in the filter expression. .EXAMPLE New-CopilotSearch -BatchQueries @("quarterly budget reports", "project planning documents") -PageSize 10 Executes multiple search queries simultaneously using batch requests. .LINK https://learn.microsoft.com/en-us/microsoft-365-copilot/extensibility/api/ai-services/search/copilotroot-search .OUTPUTS PSCustomObject #> [CmdletBinding(DefaultParameterSetName = 'SingleQuery')] [OutputType([PSCustomObject])] param( [Parameter(Mandatory = $true, ParameterSetName = 'SingleQuery')] [ValidateLength(1, 1500)] [string]$Query, [Parameter(Mandatory = $false)] [ValidateRange(1, 100)] [int]$PageSize = 25, [Parameter(Mandatory = $false)] [string]$FilterExpression, [Parameter(Mandatory = $false)] [string[]]$ResourceMetadata, [Parameter(Mandatory = $true, ParameterSetName = 'BatchQuery')] [ValidateCount(1, 20)] [string[]]$BatchQueries ) # Validate batch request limit (max 20 requests per batch) if ($PSCmdlet.ParameterSetName -eq 'BatchQuery' -and $BatchQueries.Count -gt 20) { throw "Cannot execute more than 20 queries at once. You specified $($BatchQueries.Count) queries." } try { # Check if batching is needed (multiple queries) if ($PSCmdlet.ParameterSetName -eq 'BatchQuery') { Write-Verbose "Creating batch search request for $($BatchQueries.Count) queries" $batchRequests = @() $requestId = 1 foreach ($batchQuery in $BatchQueries) { # Build individual request body $requestBody = @{ query = $batchQuery pageSize = $PageSize } # Add dataSources configuration if filter or metadata specified if ($PSBoundParameters.ContainsKey('FilterExpression') -or $PSBoundParameters.ContainsKey('ResourceMetadata')) { $oneDriveConfig = @{} if ($PSBoundParameters.ContainsKey('FilterExpression')) { $oneDriveConfig['filterExpression'] = $FilterExpression } if ($PSBoundParameters.ContainsKey('ResourceMetadata')) { $oneDriveConfig['resourceMetadataNames'] = $ResourceMetadata } $requestBody['dataSources'] = @{ oneDrive = $oneDriveConfig } } # Add to batch requests array $batchRequests += @{ id = $requestId.ToString() method = "POST" url = "/copilot/search" headers = @{ "Content-Type" = "application/json" } body = $requestBody } $requestId++ } # Create batch request payload $batchPayload = @{ requests = $batchRequests } $uri = "beta/`$batch" Write-Verbose "Sending batch search request to: $uri" $batchResponse = Invoke-MgGraphRequest -Method POST -Uri $uri -Body ($batchPayload | ConvertTo-Json -Depth 10) -OutputType PSObject # Process batch responses $allResults = @() foreach ($response in $batchResponse.responses) { $queryText = $BatchQueries[$([int]$response.id - 1)] if ($response.status -eq 200) { if ($response.body.searchHits) { Write-Verbose "Query '$queryText': Found $($response.body.totalCount) total results, returned $($response.body.searchHits.Count) in this page" # Add query information to each search hit and add to results foreach ($hit in $response.body.searchHits) { $hit | Add-Member -MemberType NoteProperty -Name "query" -Value $queryText -Force $hit | Add-Member -MemberType NoteProperty -Name "totalCount" -Value $response.body.totalCount -Force if ($response.body.'@odata.nextLink') { $hit | Add-Member -MemberType NoteProperty -Name "nextLink" -Value $response.body.'@odata.nextLink' -Force } $allResults += $hit } } else { Write-Verbose "Query '$queryText': No results found" } } else { Write-Warning "Query '$queryText': Request failed with status $($response.status)" if ($response.body.error) { Write-Warning "Error: $($response.body.error.message)" } } } if ($allResults.Count -gt 0) { Write-Verbose "Batch search completed with $($allResults.Count) total search hits" return $allResults } else { Write-Verbose "No results found from any query" return $null } } else { # Single query - use standard request Write-Verbose "Performing Copilot search for query: $Query" # Build the request body $requestBody = @{ query = $Query pageSize = $PageSize } # Add dataSources configuration if filter or metadata specified if ($PSBoundParameters.ContainsKey('FilterExpression') -or $PSBoundParameters.ContainsKey('ResourceMetadata')) { $oneDriveConfig = @{} if ($PSBoundParameters.ContainsKey('FilterExpression')) { $oneDriveConfig['filterExpression'] = $FilterExpression Write-Verbose "Filter expression: $FilterExpression" } if ($PSBoundParameters.ContainsKey('ResourceMetadata')) { $oneDriveConfig['resourceMetadataNames'] = $ResourceMetadata Write-Verbose "Resource metadata: $($ResourceMetadata -join ', ')" } $requestBody['dataSources'] = @{ oneDrive = $oneDriveConfig } } $uri = "beta/copilot/search" # Convert to JSON for debugging $jsonBody = $requestBody | ConvertTo-Json -Depth 10 Write-Verbose "Request body JSON: $jsonBody" Write-Verbose "Sending search request to: $uri" $response = Invoke-MgGraphRequest -Method POST -Uri $uri -Body $jsonBody -OutputType PSObject # Return the response with search hits if ($response.searchHits) { Write-Verbose "Found $($response.totalCount) total results, returned $($response.searchHits.Count) in this page" # Add query metadata to each search hit foreach ($hit in $response.searchHits) { $hit | Add-Member -MemberType NoteProperty -Name "query" -Value $Query -Force $hit | Add-Member -MemberType NoteProperty -Name "totalCount" -Value $response.totalCount -Force if ($response.'@odata.nextLink') { $hit | Add-Member -MemberType NoteProperty -Name "nextLink" -Value $response.'@odata.nextLink' -Force } } # Display next link information if available if ($response.'@odata.nextLink') { Write-Verbose "More results available. Use the NextLink property to retrieve additional pages." } return $response.searchHits } else { Write-Verbose "No results found for the query" return $null } } } catch { if ($_.Exception.Message -match "403" -or $_.Exception.Message -match "Forbidden") { Write-Error "Access Forbidden: The Graph API permissions Files.Read.All and Sites.Read.All are required for OneDrive search. Make sure you have the appropriate permissions. Error: $($_.Exception.Message)" } elseif ($_.Exception.Message -match "400" -or $_.Exception.Message -match "Bad Request") { Write-Error "Bad Request: The query string or parameters may be invalid. Ensure your query is properly formatted with correct spelling. Error: $($_.Exception.Message)" } else { Write-Error "Failed to perform Copilot search: $($_.Exception.Message)" } return $null } } |