Generated banner for Azure Search Nav
Generated logo for Azure Search Nav

Azure Search Nav

MicrosoftOfficialCreated by Microsoft

  • Skill
  • Tool integration
  • Cloud and DevOps
  • MIT
  • Low risk
  • Has scripts

From the author

Builds an Azure portal deep link to a specific blade/menu item for supported Microsoft.ContainerService, Microsoft.Kubernetes, and Microsoft.Compute resources using the aks-search-direct-mid semantic search API. WHEN: 'find where in the Azure portal to do something', 'search the portal for an operation', 'construct a portal link from a search query and a resource'.
— the description by Microsoft

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The azure-search-nav skill provides a PowerShell-based utility to generate direct deep links into the Azure portal. By leveraging the aks-search-direct-mid semantic search API, it allows users to translate natural-language queries into specific navigation paths for supported Microsoft.ContainerService, Microsoft.Kubernetes, and Microsoft.Compute resources. The tool handles authentication via Az.Accounts, parses resource identifiers, and constructs precise portal URLs, streamlining the process of locating specific configuration blades or menu items.

What it does

  • Parses Azure portal URLs and ARM resource IDs to extract necessary context.
  • Authenticates users via standard Azure PowerShell account modules.
  • Queries the semantic search API to map natural language to portal navigation paths.
  • Constructs and outputs direct deep links to specific resource blades.
  • Supports extensibility for additional resource types via a configuration file.
  • Provides options for both interactive browser-based and headless device-code authentication.

When to use it

  • When you need to find the exact location of a setting or operation within the Azure portal.
  • When you want to generate a shareable deep link for a specific resource blade.
  • When working with supported Container Service, Arc-enabled Kubernetes, or Compute resources.
  • When automating navigation tasks in a CLI or scripted environment.

Advantages

  • Reduces manual navigation time within the complex Azure portal interface.
  • Uses semantic search to interpret intent rather than requiring exact menu path knowledge.
  • Integrates directly with existing Azure PowerShell authentication workflows.
  • Provides a consistent, programmatic way to access deep links across different resource types.

Usage examples

“Find the image cleaner settings for my AKS cluster.”
The script queries the search API for 'image cleaner' relative to the provided cluster resource ID and returns a direct link to the corresponding portal blade.
“Generate a portal link to the virtual machine scale set networking blade.”
The tool parses the VMSS resource ID, queries the navigation index, and outputs a URL that opens the specific networking configuration page in the Azure portal.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/microsoft/azure-search-nav/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip azure-search-nav.zip -d .agents/skills

Documentation by Microsoft

Quick Reference

ItemValue
Scriptreferences/Invoke-PortalSearchNav.ps1
Resource mapreferences/resource-types.json
PrerequisiteAz.Accounts PowerShell module
AuthInteractive Microsoft sign-in (Connect-AzAccount), or device-code with -UseDeviceAuthentication

When to Use This Skill

  • User asks to find where in the Azure portal to perform an operation on a supported Container Service, Arc-enabled Kubernetes, or Compute resource.

  • User wants a portal deep link for a specific blade/menu item.

  • User provides a resource URL or ARM resource ID and a natural-language search query.

Workflow

  1. Collect resource link (portal URL or bare ARM resource ID) and search query from the user.

  2. Run references/Invoke-PortalSearchNav.ps1 -ResourceUrl '<link>' -Query '<query>'.

  3. The script signs in (browser by default; pass -UseDeviceAuthentication in headless/CLI environments), calls the search API, and prints portal deep links always rooted at https://portal.azure.com.

See references/README.md for full parameter reference and API details.

Error Handling

ErrorCauseFix
Resource type not enabledarmProvider not in references/resource-types.jsonConfirm the type is in the enabled-provider list
401 from APIInvalid token, or wrong tenant selectedRe-run and select the account with access to the target subscription's tenant
Empty resultsNo matching navigation item for the queryTry a different search term
Az.Accounts not foundModule not installedInstall-Module Az.Accounts -Scope CurrentUser
Browser sign-in fails (no window handle)Headless/CLI environmentRe-run with -UseDeviceAuthentication

Files

references/Invoke-PortalSearchNav.ps1

<#
.SYNOPSIS
    Calls the Azure portal search API for a supported Azure resource and constructs
    the resulting Azure portal deep link from the returned menu id.

.DESCRIPTION
    Given a portal link to a supported Container Service, Arc-enabled Kubernetes,
    or Compute resource and a natural-language search query, this script:
      1. Parses the ARM resource id and tenant id out of the resource link.
      2. Signs in to Azure and acquires a bearer token.
      3. POSTs a semantic search to the aks-search-direct-mid endpoint.
      4. Extracts the menu id(s) from the response and rebuilds the portal link
         against https://portal.azure.com.

.PARAMETER ResourceUrl
    A portal URL (or bare ARM resource id) pointing at a supported Azure resource.

.PARAMETER Query
    The natural-language search query, e.g. "image cleaner".

.PARAMETER ArmProvider
    Override for the armProvider filter value. Auto-derived from ResourceUrl when omitted.

.PARAMETER AppTenantId
    Value for the App-Tenant-Id header. Auto-derived from the URL or the signed-in token.

.PARAMETER Locale
    Value for the locale filter. Default "en.en-us".

.PARAMETER UseDeviceAuthentication
    Use device-code authentication instead of opening an interactive browser window.

.PARAMETER Raw
    When set, only prints the raw JSON API response (skip link construction).

.EXAMPLE
    ./Invoke-PortalSearchNav.ps1 -ResourceUrl $aksPortalLink -Query 'image cleaner'
#>
[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)]
    [string] $ResourceUrl,

    [Parameter(Mandatory = $true)]
    [string] $Query,

    [string] $ArmProvider,

    [string] $AppTenantId,

    [string] $Locale = 'en.en-us',

    [switch] $UseDeviceAuthentication,

    [switch] $Raw
)

$ErrorActionPreference = 'Stop'

# Pinned production endpoint and token audience. Not exposed as parameters.
$ApiUrl = 'https://PCNX-AI-etc5bra3fscchrew.b02.azurefd.net/aks-search-direct-mid'
$TokenResourceUrl = 'https://management.azure.com/'

# ---------------------------------------------------------------------------
# 1. Parse the resource link.
# ---------------------------------------------------------------------------
$resourcePattern = '(?i)^(?<prefix>.*?)/resource/(?<resourceId>subscriptions/(?<sub>[^/]+)/resourceGroups/(?<rg>[^/]+)/providers/(?<provider>[^/]+)/(?<type>[^/]+)/(?<name>[^/?#]+))(?<trailing>/[^?#]*)?'
$match = [regex]::Match($ResourceUrl, $resourcePattern)

if (-not $match.Success) {
    $resourcePattern = '(?i)^/?(?<resourceId>subscriptions/(?<sub>[^/]+)/resourceGroups/(?<rg>[^/]+)/providers/(?<provider>[^/]+)/(?<type>[^/]+)/(?<name>[^/?#]+))/?$'
    $match = [regex]::Match($ResourceUrl, $resourcePattern)
}

if (-not $match.Success) {
    throw "Could not parse a resource id out of -ResourceUrl. Expected a portal resource URL or '/subscriptions/.../resourceGroups/.../providers/<provider>/<type>/<name>'."
}

$resourceId     = '/' + $match.Groups['resourceId'].Value
$subscriptionId = $match.Groups['sub'].Value
$providerNs     = $match.Groups['provider'].Value
$resourceType   = $match.Groups['type'].Value

if (-not $ArmProvider) {
    $ArmProvider = "$providerNs/$resourceType"
}

# Validate and normalise armProvider casing from resource-types.json.
$resourceTypesPath = Join-Path $PSScriptRoot 'resource-types.json'
$resourceTypes = Get-Content $resourceTypesPath -Raw | ConvertFrom-Json
$armKey = $ArmProvider.ToLowerInvariant()
$entry = $resourceTypes.PSObject.Properties[$armKey]
if ($entry) {
    $ArmProvider = $entry.Value.armProvider
}
else {
    throw "Resource type '$ArmProvider' is not enabled for portal navigation search."
}

if (-not $AppTenantId) {
    $tenantMatch = [regex]::Match($ResourceUrl, '#@(?<tenant>[^/]+)')
    if ($tenantMatch.Success) {
        $AppTenantId = $tenantMatch.Groups['tenant'].Value
    }
}

# Output links are always built against the production portal host.
$prefix = if ($AppTenantId) { "https://portal.azure.com/#@$AppTenantId" } else { 'https://portal.azure.com' }

# ---------------------------------------------------------------------------
# 2. Sign in to Azure and acquire the API token.
# ---------------------------------------------------------------------------
if (-not (Get-Module -ListAvailable -Name Az.Accounts)) {
    throw "The Az.Accounts PowerShell module is required. Install it with 'Install-Module Az.Accounts -Scope CurrentUser'."
}

Import-Module Az.Accounts

$connectParameters = @{ SkipContextPopulation = $true }
if ($AppTenantId)    { $connectParameters['Tenant']       = $AppTenantId }
if ($subscriptionId) { $connectParameters['Subscription']  = $subscriptionId }
if ($UseDeviceAuthentication) { $connectParameters['UseDeviceAuthentication'] = $true }

Write-Host 'Sign in with the Microsoft account that has access to this Azure resource.' -ForegroundColor Cyan
Connect-AzAccount @connectParameters | Out-Null

$tokenParameters = @{ ResourceUrl = $TokenResourceUrl }
$azTenant = (Get-AzContext).Tenant.Id
if ($azTenant) { $tokenParameters['TenantId'] = $azTenant }
$accessToken = Get-AzAccessToken @tokenParameters

if ($accessToken.Token -is [System.Security.SecureString]) {
    $tokenCredential = [System.Net.NetworkCredential]::new('', $accessToken.Token)
    $BearerToken = $tokenCredential.Password
}
else {
    $BearerToken = [string] $accessToken.Token
}

if ([string]::IsNullOrWhiteSpace($BearerToken)) {
    throw "Azure sign-in succeeded, but no access token was returned for '$TokenResourceUrl'."
}

# The API's APIM issuer check requires App-Tenant-Id to be the GUID that
# issued the token, not the domain from the URL.
try {
    $jwtParts = $BearerToken.Split('.')
    $payloadSeg = $jwtParts[1].Replace('-','+').Replace('_','/')
    switch ($payloadSeg.Length % 4) { 2 { $payloadSeg += '==' } 3 { $payloadSeg += '=' } }
    $claims = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($payloadSeg)) | ConvertFrom-Json
    if ($claims.tid) { $AppTenantId = $claims.tid }
} catch {
    Write-Verbose "Could not decode tid from JWT: $_"
}

# ---------------------------------------------------------------------------
# 3. Build and send the request.
# ---------------------------------------------------------------------------
$payload = [ordered]@{
    search                = $Query
    count                 = 'true'
    filter                = "armProvider eq '$ArmProvider' and locale eq '$Locale'"
    queryType             = 'semantic'
    semanticConfiguration = 'semantic'
}

$headers = @{
    'Accept'              = '*/*'
    'User-Data-Boundary'  = 'Global'
}
$headers['Authorization'] = "Bearer $BearerToken"
if ($AppTenantId) {
    $headers['App-Tenant-Id'] = $AppTenantId
}

Write-Verbose "POST $ApiUrl"
Write-Verbose ($payload | ConvertTo-Json -Depth 10)

$response = Invoke-RestMethod -Uri $ApiUrl -Method Post -Headers $headers -ContentType 'application/json' -Body ($payload | ConvertTo-Json -Depth 10)

if ($Raw) {
    $response | ConvertTo-Json -Depth 20
    return
}

# ---------------------------------------------------------------------------
# 3. Find menu id(s) in the response (schema-tolerant recursive search).
# ---------------------------------------------------------------------------
function Find-MenuIdCandidates {
    param(
        [Parameter(Mandatory)] $Object,
        [string[]] $Names = @('menuId', 'menuID', 'menu_id', 'bladeName', 'blade')
    )

    $found = New-Object System.Collections.Generic.List[object]

    function Recurse($obj) {
        if ($null -eq $obj) { return }

        if ($obj -is [string]) { return }

        if ($obj -is [System.Collections.IEnumerable]) {
            foreach ($item in $obj) { Recurse $item }
            return
        }

        if ($obj -is [System.Management.Automation.PSCustomObject]) {
            $menuValue = $null
            foreach ($name in $Names) {
                $prop = $obj.PSObject.Properties[$name]
                if ($prop -and -not [string]::IsNullOrWhiteSpace([string]$prop.Value)) {
                    $menuValue = $prop.Value
                    break
                }
            }
            if ($menuValue) {
                $title = $null
                foreach ($titleName in @('title', 'name', 'displayName', 'label')) {
                    $titleProp = $obj.PSObject.Properties[$titleName]
                    if ($titleProp) { $title = $titleProp.Value; break }
                }
                $score = $null
                $scoreProp = $obj.PSObject.Properties['score']
                if ($scoreProp) { $score = $scoreProp.Value }

                $found.Add([PSCustomObject]@{ MenuId = $menuValue; Title = $title; Score = $score })
            }
            foreach ($prop in $obj.PSObject.Properties) {
                Recurse $prop.Value
            }
        }
    }

    Recurse $Object
    return $found
}

$candidates = Find-MenuIdCandidates -Object $response

if (-not $candidates -or $candidates.Count -eq 0) {
    Write-Warning 'No menu id found in the API response with the known property names (menuId, menuID, menu_id, bladeName, blade). Dumping raw response for inspection:'
    $response | ConvertTo-Json -Depth 20
    return
}

# ---------------------------------------------------------------------------
# 4. Rebuild the portal link for every candidate menu id.
# ---------------------------------------------------------------------------
$results = foreach ($candidate in $candidates) {
    [PSCustomObject]@{
        Title   = $candidate.Title
        Score   = $candidate.Score
        MenuId  = $candidate.MenuId
        PortalLink = "$prefix/resource$resourceId/$($candidate.MenuId)"
    }
}

$results | Format-Table -AutoSize -Wrap

Write-Host ''
Write-Host 'Best match portal link:' -ForegroundColor Green
Write-Host $results[0].PortalLink

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

What our scanner noticed· Low risk

  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Microsoft
Publisher
Microsoft (Official)
Source
microsoft/skills · .github/plugins/aks-skills/skills/azure-search-nav · commit 0696600
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from .github/plugins/aks-skills/LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Microsoft.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download azure-search-nav.zip