mirror of
https://github.com/github/awesome-copilot.git
synced 2026-03-12 04:05:12 +00:00
feat: add winmd-api-search skill for Windows desktop API discovery (#860)
* feat: add winmd-api-search skill for Windows desktop API discovery Add a skill that helps find and explore Windows desktop APIs (WinRT/WinAppSDK). It searches a local WinMD metadata cache to discover APIs for platform capabilities like camera, file access, notifications, UI controls, AI/ML, sensors, and networking. Includes bundled scripts: - Update-WinMdCache.ps1: generates the JSON cache from SDK and NuGet packages - Invoke-WinMdQuery.ps1: searches types, members, enums, and namespaces - cache-generator: .NET tool that parses WinMD files into JSON Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: sync winmd-api-search with latest upstream changes Updates from PowerToys source branch: - Detect installed WinAppSDK runtime via Get-AppxPackage - Respect NUGET_PACKAGES env var for global packages path - Use OS architecture for runtime package detection - Fix method/event visibility to use MemberAccessMask equality - Fix EnumerationOptions, TypeSpecification decoding, search sort - Robust scan with case-insensitive dedup and multi-path search - Deduplicate packages by (Id, Version) - Fix assets.json selection and pin SRM version - Fix SDK version sorting and global namespace handling Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: remove en-us locale from Microsoft Learn URLs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: filter assets.json by package type; unique scan-mode manifests - Filter assets.json libraries by type==package to skip project references - Append short path hash to manifest names in scan mode to avoid collisions - Support prefix match in query script for scan-mode manifest names Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -227,5 +227,6 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to
|
||||
| [webapp-testing](../skills/webapp-testing/SKILL.md) | Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs. | `test-helper.js` |
|
||||
| [what-context-needed](../skills/what-context-needed/SKILL.md) | Ask Copilot what files it needs to see before answering a question | None |
|
||||
| [winapp-cli](../skills/winapp-cli/SKILL.md) | Windows App Development CLI (winapp) for building, packaging, and deploying Windows applications. Use when asked to initialize Windows app projects, create MSIX packages, generate AppxManifest.xml, manage development certificates, add package identity for debugging, sign packages, publish to the Microsoft Store, create external catalogs, or access Windows SDK build tools. Supports .NET (csproj), C++, Electron, Rust, Tauri, and cross-platform frameworks targeting Windows. | None |
|
||||
| [winmd-api-search](../skills/winmd-api-search/SKILL.md) | Find and explore Windows desktop APIs. Use when building features that need platform capabilities — camera, file access, notifications, UI controls, AI/ML, sensors, networking, etc. Discovers the right API for a task and retrieves full type details (methods, properties, events, enumeration values). | `LICENSE.txt`<br />`scripts/Invoke-WinMdQuery.ps1`<br />`scripts/Update-WinMdCache.ps1`<br />`scripts/cache-generator/CacheGenerator.csproj`<br />`scripts/cache-generator/Directory.Build.props`<br />`scripts/cache-generator/Directory.Build.targets`<br />`scripts/cache-generator/Directory.Packages.props`<br />`scripts/cache-generator/Program.cs` |
|
||||
| [workiq-copilot](../skills/workiq-copilot/SKILL.md) | Guides the Copilot CLI on how to use the WorkIQ CLI/MCP server to query Microsoft 365 Copilot data (emails, meetings, docs, Teams, people) for live context, summaries, and recommendations. | None |
|
||||
| [write-coding-standards-from-file](../skills/write-coding-standards-from-file/SKILL.md) | Write a coding standards document for a project using the coding styles from the file(s) and/or folder(s) passed as arguments in the prompt. | None |
|
||||
|
||||
21
skills/winmd-api-search/LICENSE.txt
Normal file
21
skills/winmd-api-search/LICENSE.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
192
skills/winmd-api-search/SKILL.md
Normal file
192
skills/winmd-api-search/SKILL.md
Normal file
@@ -0,0 +1,192 @@
|
||||
---
|
||||
name: winmd-api-search
|
||||
description: 'Find and explore Windows desktop APIs. Use when building features that need platform capabilities — camera, file access, notifications, UI controls, AI/ML, sensors, networking, etc. Discovers the right API for a task and retrieves full type details (methods, properties, events, enumeration values).'
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# WinMD API Search
|
||||
|
||||
This skill helps you find the right Windows API for any capability and get its full details. It searches a local cache of all WinMD metadata from:
|
||||
|
||||
- **Windows Platform SDK** — all `Windows.*` WinRT APIs (always available, no restore needed)
|
||||
- **WinAppSDK / WinUI** — bundled as a baseline in the cache generator (always available, no restore needed)
|
||||
- **NuGet packages** — any additional packages in restored projects that contain `.winmd` files
|
||||
- **Project-output WinMD** — class libraries (C++/WinRT, C#) that produce `.winmd` as build output
|
||||
|
||||
Even on a fresh clone with no restore or build, you still get full Platform SDK + WinAppSDK coverage.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- User wants to build a feature and you need to find which API provides that capability
|
||||
- User asks "how do I do X?" where X involves a platform feature (camera, files, notifications, sensors, AI, etc.)
|
||||
- You need the exact methods, properties, events, or enumeration values of a type before writing code
|
||||
- You're unsure which control, class, or interface to use for a UI or system task
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **.NET SDK 8.0 or later** — required to build the cache generator. Install from [dotnet.microsoft.com](https://dotnet.microsoft.com/download) if not available.
|
||||
|
||||
## Cache Setup (Required Before First Use)
|
||||
|
||||
All query and search commands read from a local JSON cache. **You must generate the cache before running any queries.**
|
||||
|
||||
```powershell
|
||||
# All projects in the repo (recommended for first run)
|
||||
.\.github\skills\winmd-api-search\scripts\Update-WinMdCache.ps1
|
||||
|
||||
# Single project
|
||||
.\.github\skills\winmd-api-search\scripts\Update-WinMdCache.ps1 -ProjectDir <project-folder>
|
||||
```
|
||||
|
||||
No project restore or build is needed for baseline coverage (Platform SDK + WinAppSDK). For additional NuGet packages, the project needs `dotnet restore` (which generates `project.assets.json`) or a `packages.config` file.
|
||||
|
||||
Cache is stored at `Generated Files\winmd-cache\`, deduplicated per-package+version.
|
||||
|
||||
### What gets indexed
|
||||
|
||||
| Source | When available |
|
||||
|--------|----------------|
|
||||
| Windows Platform SDK | Always (reads from local SDK install) |
|
||||
| WinAppSDK (latest) | Always (bundled as baseline in cache generator) |
|
||||
| WinAppSDK Runtime | When installed on the system (detected via `Get-AppxPackage`) |
|
||||
| Project NuGet packages | After `dotnet restore` or with `packages.config` |
|
||||
| Project-output `.winmd` | After project build (class libraries that produce WinMD) |
|
||||
|
||||
> **Note:** This cache directory should be in `.gitignore` — it's generated, not source.
|
||||
|
||||
## How to Use
|
||||
|
||||
Pick the path that matches the situation:
|
||||
|
||||
---
|
||||
|
||||
### Discover — "I don't know which API to use"
|
||||
|
||||
The user describes a capability in their own words. You need to find the right API.
|
||||
|
||||
**0. Ensure the cache exists**
|
||||
|
||||
If the cache hasn't been generated yet, run `Update-WinMdCache.ps1` first — see [Cache Setup](#cache-setup-required-before-first-use) above.
|
||||
|
||||
**1. Translate user language → search keywords**
|
||||
|
||||
Map the user's daily language to programming terms. Try multiple variations:
|
||||
|
||||
| User says | Search keywords to try (in order) |
|
||||
|-----------|-----------------------------------|
|
||||
| "take a picture" | `camera`, `capture`, `photo`, `MediaCapture` |
|
||||
| "load from disk" | `file open`, `picker`, `FileOpen`, `StorageFile` |
|
||||
| "describe what's in it" | `image description`, `Vision`, `Recognition` |
|
||||
| "show a popup" | `dialog`, `flyout`, `popup`, `ContentDialog` |
|
||||
| "drag and drop" | `drag`, `drop`, `DragDrop` |
|
||||
| "save settings" | `settings`, `ApplicationData`, `LocalSettings` |
|
||||
|
||||
Start with simple everyday words. If results are weak or irrelevant, try the more technical variation.
|
||||
|
||||
**2. Run searches**
|
||||
|
||||
```powershell
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action search -Query "<keyword>"
|
||||
```
|
||||
|
||||
This returns ranked namespaces with top matching types and the **JSON file path**.
|
||||
|
||||
If results have **low scores (below 60) or are irrelevant**, fall back to searching online documentation:
|
||||
|
||||
1. Use web search to find the right API on Microsoft Learn, for example:
|
||||
- `site:learn.microsoft.com/uwp/api <capability keywords>` for `Windows.*` APIs
|
||||
- `site:learn.microsoft.com/windows/windows-app-sdk/api/winrt <capability keywords>` for `Microsoft.*` WinAppSDK APIs
|
||||
2. Read the documentation pages to identify which type matches the user's requirement.
|
||||
3. Once you know the type name, come back and use `-Action members` or `-Action enums` to get the exact local signatures.
|
||||
|
||||
**3. Read the JSON to choose the right API**
|
||||
|
||||
Read the file at the path(s) from the top results. The JSON has all types in that namespace — full members, signatures, parameters, return types, enumeration values.
|
||||
|
||||
Read and decide which types and members fit the user's requirement.
|
||||
|
||||
**4. Look up official documentation for context**
|
||||
|
||||
The cache contains only signatures — no descriptions or usage guidance. For explanations, examples, and remarks, look up the type on Microsoft Learn:
|
||||
|
||||
| Namespace prefix | Documentation base URL |
|
||||
|-----------------|----------------------|
|
||||
| `Windows.*` | `https://learn.microsoft.com/uwp/api/{fully.qualified.typename}` |
|
||||
| `Microsoft.*` (WinAppSDK) | `https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/{fully.qualified.typename}` |
|
||||
|
||||
For example, `Microsoft.UI.Xaml.Controls.NavigationView` maps to:
|
||||
`https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.navigationview`
|
||||
|
||||
**5. Use the API knowledge to answer or write code**
|
||||
|
||||
---
|
||||
|
||||
### Lookup — "I know the API, show me the details"
|
||||
|
||||
You already know (or suspect) the type or namespace name. Go direct:
|
||||
|
||||
```powershell
|
||||
# Get all members of a known type
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action members -TypeName "Microsoft.UI.Xaml.Controls.NavigationView"
|
||||
|
||||
# Get enum values
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action enums -TypeName "Microsoft.UI.Xaml.Visibility"
|
||||
|
||||
# List all types in a namespace
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action types -Namespace "Microsoft.UI.Xaml.Controls"
|
||||
|
||||
# Browse namespaces
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action namespaces -Filter "Microsoft.UI"
|
||||
```
|
||||
|
||||
If you need full detail beyond what `-Action members` shows, use `-Action search` to get the JSON file path, then read the JSON file directly.
|
||||
|
||||
---
|
||||
|
||||
### Other Commands
|
||||
|
||||
```powershell
|
||||
# List cached projects
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action projects
|
||||
|
||||
# List packages for a project
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action packages
|
||||
|
||||
# Show stats
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action stats
|
||||
```
|
||||
|
||||
> If only one project is cached, `-Project` is auto-selected.
|
||||
> If multiple projects exist, add `-Project <name>` (use `-Action projects` to see available names).
|
||||
> In scan mode, manifest names include a short hash suffix to avoid collisions; you can pass the base project name without the suffix if it's unambiguous.
|
||||
|
||||
## Search Scoring
|
||||
|
||||
The search ranks type names and member names against your query:
|
||||
|
||||
| Score | Match type | Example |
|
||||
|-------|-----------|---------|
|
||||
| 100 | Exact name | `Button` → `Button` |
|
||||
| 80 | Starts with | `Navigation` → `NavigationView` |
|
||||
| 60 | Contains | `Dialog` → `ContentDialog` |
|
||||
| 50 | PascalCase initials | `ASB` → `AutoSuggestBox` |
|
||||
| 40 | Multi-keyword AND | `navigation item` → `NavigationViewItem` |
|
||||
| 20 | Fuzzy character match | `NavVw` → `NavigationView` |
|
||||
|
||||
Results are grouped by namespace. Higher-scored namespaces appear first.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| "Cache not found" | Run `Update-WinMdCache.ps1` |
|
||||
| "Multiple projects cached" | Add `-Project <name>` |
|
||||
| "Namespace not found" | Use `-Action namespaces` to list available ones |
|
||||
| "Type not found" | Use fully qualified name (e.g., `Microsoft.UI.Xaml.Controls.Button`) |
|
||||
| Stale after NuGet update | Re-run `Update-WinMdCache.ps1` |
|
||||
| Cache in git history | Add `Generated Files/` to `.gitignore` |
|
||||
|
||||
## References
|
||||
|
||||
- [Windows Platform SDK API reference](https://learn.microsoft.com/uwp/api/) — documentation for `Windows.*` namespaces
|
||||
- [Windows App SDK API reference](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/) — documentation for `Microsoft.*` WinAppSDK namespaces
|
||||
505
skills/winmd-api-search/scripts/Invoke-WinMdQuery.ps1
Normal file
505
skills/winmd-api-search/scripts/Invoke-WinMdQuery.ps1
Normal file
@@ -0,0 +1,505 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Query WinMD API metadata from cached JSON files.
|
||||
|
||||
.DESCRIPTION
|
||||
Reads pre-built JSON cache of WinMD types, members, and namespaces.
|
||||
The cache is organized per-package (deduplicated) with project manifests
|
||||
that map each project to its referenced packages.
|
||||
|
||||
Supports listing namespaces, types, members, searching, enum value lookup,
|
||||
and listing cached projects/packages.
|
||||
|
||||
.PARAMETER Action
|
||||
The query action to perform:
|
||||
- projects : List cached projects
|
||||
- packages : List packages for a project
|
||||
- stats : Show aggregate statistics for a project
|
||||
- namespaces : List all namespaces (optional -Filter prefix)
|
||||
- types : List types in a namespace (-Namespace required)
|
||||
- members : List members of a type (-TypeName required)
|
||||
- search : Search types and members by name (-Query required)
|
||||
- enums : List enum values (-TypeName required)
|
||||
|
||||
.PARAMETER Project
|
||||
Project name to query. Auto-selected if only one project is cached.
|
||||
Use -Action projects to list available projects.
|
||||
|
||||
.PARAMETER Namespace
|
||||
Namespace to query types from (used with -Action types).
|
||||
|
||||
.PARAMETER TypeName
|
||||
Full type name e.g. "Microsoft.UI.Xaml.Controls.Button" (used with -Action members, enums).
|
||||
|
||||
.PARAMETER Query
|
||||
Search query string (used with -Action search).
|
||||
|
||||
.PARAMETER Filter
|
||||
Optional prefix filter for namespaces (used with -Action namespaces).
|
||||
|
||||
.PARAMETER CacheDir
|
||||
Path to the winmd-cache directory. Defaults to "Generated Files\winmd-cache"
|
||||
relative to the workspace root.
|
||||
|
||||
.PARAMETER MaxResults
|
||||
Maximum number of results to return for search. Defaults to 30.
|
||||
|
||||
.EXAMPLE
|
||||
.\Invoke-WinMdQuery.ps1 -Action projects
|
||||
.\Invoke-WinMdQuery.ps1 -Action packages -Project BlankWinUI
|
||||
.\Invoke-WinMdQuery.ps1 -Action stats -Project BlankWinUI
|
||||
.\Invoke-WinMdQuery.ps1 -Action namespaces -Filter "Microsoft.UI"
|
||||
.\Invoke-WinMdQuery.ps1 -Action types -Namespace "Microsoft.UI.Xaml.Controls"
|
||||
.\Invoke-WinMdQuery.ps1 -Action members -TypeName "Microsoft.UI.Xaml.Controls.Button"
|
||||
.\Invoke-WinMdQuery.ps1 -Action search -Query "NavigationView"
|
||||
.\Invoke-WinMdQuery.ps1 -Action enums -TypeName "Microsoft.UI.Xaml.Visibility"
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateSet('projects', 'packages', 'stats', 'namespaces', 'types', 'members', 'search', 'enums')]
|
||||
[string]$Action,
|
||||
|
||||
[string]$Project,
|
||||
[string]$Namespace,
|
||||
[string]$TypeName,
|
||||
[string]$Query,
|
||||
[string]$Filter,
|
||||
[string]$CacheDir,
|
||||
[int]$MaxResults = 30
|
||||
)
|
||||
|
||||
# ─── Resolve cache directory ─────────────────────────────────────────────────
|
||||
|
||||
if (-not $CacheDir) {
|
||||
# Convention: skill lives at .github/skills/winmd-api-search/scripts/
|
||||
# so workspace root is 4 levels up from $PSScriptRoot.
|
||||
$scriptDir = $PSScriptRoot
|
||||
$root = (Resolve-Path (Join-Path $scriptDir '..\..\..\..')).Path
|
||||
$CacheDir = Join-Path $root 'Generated Files\winmd-cache'
|
||||
}
|
||||
|
||||
if (-not (Test-Path $CacheDir)) {
|
||||
Write-Error "Cache not found at: $CacheDir`nRun: .\Update-WinMdCache.ps1 (from .github\skills\winmd-api-search\scripts\)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ─── Project resolution helpers ──────────────────────────────────────────────
|
||||
|
||||
function Get-CachedProjects {
|
||||
$projectsDir = Join-Path $CacheDir 'projects'
|
||||
if (-not (Test-Path $projectsDir)) { return @() }
|
||||
Get-ChildItem $projectsDir -Filter '*.json' | ForEach-Object { $_.BaseName }
|
||||
}
|
||||
|
||||
function Resolve-ProjectManifest {
|
||||
param([string]$Name)
|
||||
|
||||
$projectsDir = Join-Path $CacheDir 'projects'
|
||||
if (-not (Test-Path $projectsDir)) {
|
||||
Write-Error "No projects cached. Run Update-WinMdCache.ps1 first."
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($Name) {
|
||||
$path = Join-Path $projectsDir "$Name.json"
|
||||
if (-not (Test-Path $path)) {
|
||||
# Scan mode appends a hash suffix -- try prefix match
|
||||
$matching = @(Get-ChildItem $projectsDir -Filter "${Name}_*.json" -ErrorAction SilentlyContinue)
|
||||
if ($matching.Count -eq 1) {
|
||||
return Get-Content $matching[0].FullName -Raw | ConvertFrom-Json
|
||||
}
|
||||
if ($matching.Count -gt 1) {
|
||||
$names = ($matching | ForEach-Object { $_.BaseName }) -join ', '
|
||||
Write-Error "Multiple projects match '$Name'. Specify the full name: $names"
|
||||
exit 1
|
||||
}
|
||||
$available = (Get-CachedProjects) -join ', '
|
||||
Write-Error "Project '$Name' not found. Available: $available"
|
||||
exit 1
|
||||
}
|
||||
return Get-Content $path -Raw | ConvertFrom-Json
|
||||
}
|
||||
|
||||
# Auto-select if only one project
|
||||
$manifests = Get-ChildItem $projectsDir -Filter '*.json' -ErrorAction SilentlyContinue
|
||||
if ($manifests.Count -eq 0) {
|
||||
Write-Error "No projects cached. Run Update-WinMdCache.ps1 first."
|
||||
exit 1
|
||||
}
|
||||
if ($manifests.Count -eq 1) {
|
||||
return Get-Content $manifests[0].FullName -Raw | ConvertFrom-Json
|
||||
}
|
||||
|
||||
$available = ($manifests | ForEach-Object { $_.BaseName }) -join ', '
|
||||
Write-Error "Multiple projects cached -- use -Project to specify. Available: $available"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function Get-PackageCacheDirs {
|
||||
param($Manifest)
|
||||
$dirs = @()
|
||||
foreach ($pkg in $Manifest.packages) {
|
||||
$dir = Join-Path (Join-Path (Join-Path $CacheDir 'packages') $pkg.id) $pkg.version
|
||||
if (Test-Path $dir) {
|
||||
$dirs += $dir
|
||||
}
|
||||
}
|
||||
return $dirs
|
||||
}
|
||||
|
||||
# ─── Action: projects ────────────────────────────────────────────────────────
|
||||
|
||||
function Show-Projects {
|
||||
$projects = Get-CachedProjects
|
||||
if ($projects.Count -eq 0) {
|
||||
Write-Output "No projects cached."
|
||||
return
|
||||
}
|
||||
Write-Output "Cached projects ($($projects.Count)):"
|
||||
foreach ($p in $projects) {
|
||||
$manifest = Get-Content (Join-Path (Join-Path $CacheDir 'projects') "$p.json") -Raw | ConvertFrom-Json
|
||||
$pkgCount = $manifest.packages.Count
|
||||
Write-Output " $p ($pkgCount package(s))"
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Action: packages ────────────────────────────────────────────────────────
|
||||
|
||||
function Show-Packages {
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
Write-Output "Packages for project '$($manifest.projectName)' ($($manifest.packages.Count)):"
|
||||
foreach ($pkg in $manifest.packages) {
|
||||
$metaPath = Join-Path (Join-Path (Join-Path (Join-Path $CacheDir 'packages') $pkg.id) $pkg.version) 'meta.json'
|
||||
if (Test-Path $metaPath) {
|
||||
$meta = Get-Content $metaPath -Raw | ConvertFrom-Json
|
||||
Write-Output " $($pkg.id)@$($pkg.version) -- $($meta.totalTypes) types, $($meta.totalMembers) members"
|
||||
} else {
|
||||
Write-Output " $($pkg.id)@$($pkg.version) -- (cache missing)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Action: stats ───────────────────────────────────────────────────────────
|
||||
|
||||
function Show-Stats {
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$totalTypes = 0
|
||||
$totalMembers = 0
|
||||
$totalNamespaces = 0
|
||||
$totalWinMd = 0
|
||||
|
||||
foreach ($pkg in $manifest.packages) {
|
||||
$metaPath = Join-Path (Join-Path (Join-Path (Join-Path $CacheDir 'packages') $pkg.id) $pkg.version) 'meta.json'
|
||||
if (Test-Path $metaPath) {
|
||||
$meta = Get-Content $metaPath -Raw | ConvertFrom-Json
|
||||
$totalTypes += $meta.totalTypes
|
||||
$totalMembers += $meta.totalMembers
|
||||
$totalNamespaces += $meta.totalNamespaces
|
||||
$totalWinMd += $meta.winMdFiles.Count
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output "WinMD Index Statistics -- $($manifest.projectName)"
|
||||
Write-Output "======================================"
|
||||
Write-Output " Packages: $($manifest.packages.Count)"
|
||||
Write-Output " Namespaces: $totalNamespaces (may overlap across packages)"
|
||||
Write-Output " Types: $totalTypes"
|
||||
Write-Output " Members: $totalMembers"
|
||||
Write-Output " WinMD files: $totalWinMd"
|
||||
}
|
||||
|
||||
# ─── Action: namespaces ──────────────────────────────────────────────────────
|
||||
|
||||
function Get-Namespaces {
|
||||
param([string]$Prefix)
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$dirs = Get-PackageCacheDirs -Manifest $manifest
|
||||
$allNs = @()
|
||||
|
||||
foreach ($dir in $dirs) {
|
||||
$nsFile = Join-Path $dir 'namespaces.json'
|
||||
if (Test-Path $nsFile) {
|
||||
$allNs += (Get-Content $nsFile -Raw | ConvertFrom-Json)
|
||||
}
|
||||
}
|
||||
|
||||
$allNs = $allNs | Sort-Object -Unique
|
||||
if ($Prefix) {
|
||||
$allNs = $allNs | Where-Object { $_ -like "$Prefix*" }
|
||||
}
|
||||
$allNs | ForEach-Object { Write-Output $_ }
|
||||
}
|
||||
|
||||
# ─── Action: types ───────────────────────────────────────────────────────────
|
||||
|
||||
function Get-TypesInNamespace {
|
||||
param([string]$Ns)
|
||||
if (-not $Ns) {
|
||||
Write-Error "-Namespace is required for 'types' action."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$dirs = Get-PackageCacheDirs -Manifest $manifest
|
||||
$safeFile = $Ns.Replace('.', '_') + '.json'
|
||||
$found = $false
|
||||
$seen = @{}
|
||||
|
||||
foreach ($dir in $dirs) {
|
||||
$filePath = Join-Path $dir "types\$safeFile"
|
||||
if (-not (Test-Path $filePath)) { continue }
|
||||
$found = $true
|
||||
$types = Get-Content $filePath -Raw | ConvertFrom-Json
|
||||
foreach ($t in $types) {
|
||||
if ($seen.ContainsKey($t.fullName)) { continue }
|
||||
$seen[$t.fullName] = $true
|
||||
Write-Output "$($t.kind) $($t.fullName)$(if ($t.baseType) { " : $($t.baseType)" } else { '' })"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $found) {
|
||||
Write-Error "Namespace not found: $Ns"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Action: members ─────────────────────────────────────────────────────────
|
||||
|
||||
function Get-MembersOfType {
|
||||
param([string]$FullName)
|
||||
if (-not $FullName) {
|
||||
Write-Error "-TypeName is required for 'members' action."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$lastDot = $FullName.LastIndexOf('.')
|
||||
if ($lastDot -lt 0) {
|
||||
Write-Error "-TypeName must include a namespace (for example: 'MyNamespace.MyType'). Provided: $FullName"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$ns = $FullName.Substring(0, $lastDot)
|
||||
$safeFile = $ns.Replace('.', '_') + '.json'
|
||||
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$dirs = Get-PackageCacheDirs -Manifest $manifest
|
||||
|
||||
foreach ($dir in $dirs) {
|
||||
$filePath = Join-Path $dir "types\$safeFile"
|
||||
if (-not (Test-Path $filePath)) { continue }
|
||||
|
||||
$types = Get-Content $filePath -Raw | ConvertFrom-Json
|
||||
$type = $types | Where-Object { $_.fullName -eq $FullName }
|
||||
if (-not $type) { continue }
|
||||
|
||||
Write-Output "$($type.kind) $($type.fullName)"
|
||||
if ($type.baseType) { Write-Output " Extends: $($type.baseType)" }
|
||||
Write-Output ""
|
||||
foreach ($m in $type.members) {
|
||||
Write-Output " [$($m.kind)] $($m.signature)"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Write-Error "Type not found: $FullName"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ─── Action: search ──────────────────────────────────────────────────────────
|
||||
# Ranks namespaces by best match score on type names and member names.
|
||||
# Outputs: ranked namespaces with top matching types and the JSON file path.
|
||||
# The agent can then read the JSON file to inspect all members intelligently.
|
||||
|
||||
function Search-WinMd {
|
||||
param([string]$SearchQuery, [int]$Max)
|
||||
if (-not $SearchQuery) {
|
||||
Write-Error "-Query is required for 'search' action."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$dirs = Get-PackageCacheDirs -Manifest $manifest
|
||||
|
||||
# Collect: namespace -> { bestScore, matchingTypes[], filePath }
|
||||
$nsResults = @{}
|
||||
|
||||
foreach ($dir in $dirs) {
|
||||
$nsFile = Join-Path $dir 'namespaces.json'
|
||||
if (-not (Test-Path $nsFile)) { continue }
|
||||
$nsList = Get-Content $nsFile -Raw | ConvertFrom-Json
|
||||
|
||||
foreach ($n in $nsList) {
|
||||
$safeFile = $n.Replace('.', '_') + '.json'
|
||||
$filePath = Join-Path $dir "types\$safeFile"
|
||||
if (-not (Test-Path $filePath)) { continue }
|
||||
|
||||
$types = Get-Content $filePath -Raw | ConvertFrom-Json
|
||||
foreach ($t in $types) {
|
||||
$typeScore = Get-MatchScore -Name $t.name -FullName $t.fullName -Query $SearchQuery
|
||||
|
||||
# Also search member names for matches
|
||||
$bestMemberScore = 0
|
||||
$matchingMember = $null
|
||||
if ($t.members) {
|
||||
foreach ($m in $t.members) {
|
||||
$memberName = $m.name
|
||||
$mScore = Get-MatchScore -Name $memberName -FullName "$($t.fullName).$memberName" -Query $SearchQuery
|
||||
if ($mScore -gt $bestMemberScore) {
|
||||
$bestMemberScore = $mScore
|
||||
$matchingMember = $m.signature
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$score = [Math]::Max($typeScore, $bestMemberScore)
|
||||
if ($score -le 0) { continue }
|
||||
|
||||
if (-not $nsResults.ContainsKey($n)) {
|
||||
$nsResults[$n] = @{ BestScore = 0; Types = @(); FilePaths = @() }
|
||||
}
|
||||
$entry = $nsResults[$n]
|
||||
if ($score -gt $entry.BestScore) { $entry.BestScore = $score }
|
||||
if ($entry.FilePaths -notcontains $filePath) {
|
||||
$entry.FilePaths += $filePath
|
||||
}
|
||||
|
||||
if ($typeScore -ge $bestMemberScore) {
|
||||
$entry.Types += @{ Text = "$($t.kind) $($t.fullName) [$typeScore]"; Score = $typeScore }
|
||||
} else {
|
||||
$entry.Types += @{ Text = "$($t.kind) $($t.fullName) -> $matchingMember [$bestMemberScore]"; Score = $bestMemberScore }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($nsResults.Count -eq 0) {
|
||||
Write-Output "No results found for: $SearchQuery"
|
||||
return
|
||||
}
|
||||
|
||||
$ranked = $nsResults.GetEnumerator() |
|
||||
Sort-Object { $_.Value.BestScore } -Descending |
|
||||
Select-Object -First $Max
|
||||
|
||||
foreach ($r in $ranked) {
|
||||
$ns = $r.Key
|
||||
$info = $r.Value
|
||||
Write-Output "[$($info.BestScore)] $ns"
|
||||
foreach ($fp in $info.FilePaths) {
|
||||
Write-Output " File: $fp"
|
||||
}
|
||||
# Show top 5 highest-scoring matching types in this namespace
|
||||
$info.Types | Sort-Object { $_.Score } -Descending |
|
||||
Select-Object -First 5 |
|
||||
ForEach-Object { Write-Output " $($_.Text)" }
|
||||
Write-Output ""
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Search scoring ──────────────────────────────────────────────────────────
|
||||
# Simple ranked scoring on type names. Higher = better.
|
||||
# 100 = exact name 80 = starts-with 60 = substring
|
||||
# 50 = PascalCase 40 = multi-keyword 20 = fuzzy subsequence
|
||||
|
||||
function Get-MatchScore {
|
||||
param([string]$Name, [string]$FullName, [string]$Query)
|
||||
|
||||
$q = $Query.Trim()
|
||||
if (-not $q) { return 0 }
|
||||
|
||||
if ($Name -eq $q) { return 100 }
|
||||
if ($Name -like "$q*") { return 80 }
|
||||
if ($Name -like "*$q*" -or $FullName -like "*$q*") { return 60 }
|
||||
|
||||
$initials = ($Name.ToCharArray() | Where-Object { [char]::IsUpper($_) }) -join ''
|
||||
if ($initials.Length -ge 2 -and $initials -like "*$q*") { return 50 }
|
||||
|
||||
$words = $q -split '\s+' | Where-Object { $_.Length -gt 0 }
|
||||
if ($words.Count -gt 1) {
|
||||
$allFound = $true
|
||||
foreach ($w in $words) {
|
||||
if ($Name -notlike "*$w*" -and $FullName -notlike "*$w*") {
|
||||
$allFound = $false
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($allFound) { return 40 }
|
||||
}
|
||||
|
||||
if (Test-FuzzySubsequence -Text $Name -Pattern $q) { return 20 }
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function Test-FuzzySubsequence {
|
||||
param([string]$Text, [string]$Pattern)
|
||||
$ti = 0
|
||||
$tLower = $Text.ToLowerInvariant()
|
||||
$pLower = $Pattern.ToLowerInvariant()
|
||||
foreach ($ch in $pLower.ToCharArray()) {
|
||||
$idx = $tLower.IndexOf($ch, $ti)
|
||||
if ($idx -lt 0) { return $false }
|
||||
$ti = $idx + 1
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
# ─── Action: enums ───────────────────────────────────────────────────────────
|
||||
|
||||
function Get-EnumValues {
|
||||
param([string]$FullName)
|
||||
if (-not $FullName) {
|
||||
Write-Error "-TypeName is required for 'enums' action."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$lastDot = $FullName.LastIndexOf('.')
|
||||
if ($lastDot -lt 1) {
|
||||
Write-Error "-TypeName must be a fully-qualified type name including namespace, e.g. 'Namespace.TypeName'. Provided: $FullName"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$ns = $FullName.Substring(0, $lastDot)
|
||||
$safeFile = $ns.Replace('.', '_') + '.json'
|
||||
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$dirs = Get-PackageCacheDirs -Manifest $manifest
|
||||
|
||||
foreach ($dir in $dirs) {
|
||||
$filePath = Join-Path $dir "types\$safeFile"
|
||||
if (-not (Test-Path $filePath)) { continue }
|
||||
|
||||
$types = Get-Content $filePath -Raw | ConvertFrom-Json
|
||||
$type = $types | Where-Object { $_.fullName -eq $FullName }
|
||||
if (-not $type) { continue }
|
||||
|
||||
if ($type.kind -ne 'Enum') {
|
||||
Write-Error "$FullName is not an Enum (kind: $($type.kind))"
|
||||
exit 1
|
||||
}
|
||||
Write-Output "Enum $($type.fullName)"
|
||||
if ($type.enumValues) {
|
||||
$type.enumValues | ForEach-Object { Write-Output " $_" }
|
||||
} else {
|
||||
Write-Output " (no values)"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Write-Error "Type not found: $FullName"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ─── Dispatch ─────────────────────────────────────────────────────────────────
|
||||
|
||||
switch ($Action) {
|
||||
'projects' { Show-Projects }
|
||||
'packages' { Show-Packages }
|
||||
'stats' { Show-Stats }
|
||||
'namespaces' { Get-Namespaces -Prefix $Filter }
|
||||
'types' { Get-TypesInNamespace -Ns $Namespace }
|
||||
'members' { Get-MembersOfType -FullName $TypeName }
|
||||
'search' { Search-WinMd -SearchQuery $Query -Max $MaxResults }
|
||||
'enums' { Get-EnumValues -FullName $TypeName }
|
||||
}
|
||||
208
skills/winmd-api-search/scripts/Update-WinMdCache.ps1
Normal file
208
skills/winmd-api-search/scripts/Update-WinMdCache.ps1
Normal file
@@ -0,0 +1,208 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Generate or refresh the WinMD cache for the Agent Skill.
|
||||
|
||||
.DESCRIPTION
|
||||
Builds and runs the standalone cache generator to export cached JSON files
|
||||
from all WinMD metadata found in project NuGet packages and Windows SDK.
|
||||
|
||||
The cache is per-package+version: if two projects reference the same
|
||||
package at the same version, the WinMD data is parsed once and shared.
|
||||
|
||||
Supports single project or recursive scan of an entire repo.
|
||||
|
||||
.PARAMETER ProjectDir
|
||||
Path to a project directory (contains .csproj/.vcxproj), or a project file itself.
|
||||
Defaults to scanning the workspace root.
|
||||
|
||||
.PARAMETER Scan
|
||||
Recursively discover all .csproj/.vcxproj files under ProjectDir.
|
||||
|
||||
.PARAMETER OutputDir
|
||||
Path to the cache output directory. Defaults to "Generated Files\winmd-cache".
|
||||
|
||||
.EXAMPLE
|
||||
.\Update-WinMdCache.ps1
|
||||
.\Update-WinMdCache.ps1 -ProjectDir BlankWinUI
|
||||
.\Update-WinMdCache.ps1 -Scan -ProjectDir .
|
||||
.\Update-WinMdCache.ps1 -ProjectDir "src\MyApp\MyApp.csproj"
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$ProjectDir,
|
||||
[switch]$Scan,
|
||||
[string]$OutputDir = 'Generated Files\winmd-cache'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Convention: skill lives at .github/skills/winmd-api-search/scripts/
|
||||
# so workspace root is 4 levels up from $PSScriptRoot.
|
||||
$root = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..\..')).Path
|
||||
$generatorProj = Join-Path (Join-Path $PSScriptRoot 'cache-generator') 'CacheGenerator.csproj'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# WinAppSDK version detection -- look only at the repo root folder (no recursion)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Get-WinAppSdkVersionFromDirectoryPackagesProps {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Extract Microsoft.WindowsAppSDK version from a Directory.Packages.props
|
||||
(Central Package Management) at the repo root.
|
||||
#>
|
||||
param([string]$RepoRoot)
|
||||
$propsFile = Join-Path $RepoRoot 'Directory.Packages.props'
|
||||
if (-not (Test-Path $propsFile)) { return $null }
|
||||
try {
|
||||
[xml]$xml = Get-Content $propsFile -Raw
|
||||
$node = $xml.SelectNodes('//PackageVersion') |
|
||||
Where-Object { $_.Include -eq 'Microsoft.WindowsAppSDK' } |
|
||||
Select-Object -First 1
|
||||
if ($node) { return $node.Version }
|
||||
} catch {
|
||||
Write-Verbose "Could not parse $propsFile : $_"
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-WinAppSdkVersionFromPackagesConfig {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Extract Microsoft.WindowsAppSDK version from a packages.config at the repo root.
|
||||
#>
|
||||
param([string]$RepoRoot)
|
||||
$configFile = Join-Path $RepoRoot 'packages.config'
|
||||
if (-not (Test-Path $configFile)) { return $null }
|
||||
try {
|
||||
[xml]$xml = Get-Content $configFile -Raw
|
||||
$node = $xml.SelectNodes('//package') |
|
||||
Where-Object { $_.id -eq 'Microsoft.WindowsAppSDK' } |
|
||||
Select-Object -First 1
|
||||
if ($node) { return $node.version }
|
||||
} catch {
|
||||
Write-Verbose "Could not parse $configFile : $_"
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
# Try Directory.Packages.props first (CPM), then packages.config
|
||||
$winAppSdkVersion = Get-WinAppSdkVersionFromDirectoryPackagesProps -RepoRoot $root
|
||||
if (-not $winAppSdkVersion) {
|
||||
$winAppSdkVersion = Get-WinAppSdkVersionFromPackagesConfig -RepoRoot $root
|
||||
}
|
||||
if ($winAppSdkVersion) {
|
||||
Write-Host "Detected WinAppSDK version from repo: $winAppSdkVersion" -ForegroundColor Cyan
|
||||
} else {
|
||||
Write-Host "No WinAppSDK version found at repo root; will use latest (Version=*)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Default: if no ProjectDir, scan the workspace root
|
||||
if (-not $ProjectDir) {
|
||||
$ProjectDir = $root
|
||||
$Scan = $true
|
||||
}
|
||||
|
||||
Push-Location $root
|
||||
|
||||
try {
|
||||
# Detect installed .NET SDK -- require >= 8.0, prefer stable over preview
|
||||
$dotnetSdks = dotnet --list-sdks 2>$null
|
||||
$bestMajor = $dotnetSdks |
|
||||
Where-Object { $_ -notmatch 'preview|rc|alpha|beta' } |
|
||||
ForEach-Object { if ($_ -match '^(\d+)\.') { [int]$Matches[1] } } |
|
||||
Where-Object { $_ -ge 8 } |
|
||||
Sort-Object -Descending |
|
||||
Select-Object -First 1
|
||||
|
||||
# Fall back to preview SDKs if no stable SDK found
|
||||
if (-not $bestMajor) {
|
||||
$bestMajor = $dotnetSdks |
|
||||
ForEach-Object { if ($_ -match '^(\d+)\.') { [int]$Matches[1] } } |
|
||||
Where-Object { $_ -ge 8 } |
|
||||
Sort-Object -Descending |
|
||||
Select-Object -First 1
|
||||
}
|
||||
|
||||
if (-not $bestMajor) {
|
||||
Write-Error "No .NET SDK >= 8.0 found. Install from https://dotnet.microsoft.com/download"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$targetFramework = "net$bestMajor.0"
|
||||
Write-Host "Using .NET SDK: $targetFramework" -ForegroundColor Cyan
|
||||
|
||||
# Build MSBuild properties -- pass detected WinAppSDK version when available
|
||||
$sdkVersionProp = ''
|
||||
if ($winAppSdkVersion) {
|
||||
$sdkVersionProp = "-p:WinAppSdkVersion=$winAppSdkVersion"
|
||||
}
|
||||
|
||||
Write-Host "Building cache generator..." -ForegroundColor Cyan
|
||||
$restoreArgs = @($generatorProj, "-p:TargetFramework=$targetFramework", '--nologo', '-v', 'q')
|
||||
if ($sdkVersionProp) { $restoreArgs += $sdkVersionProp }
|
||||
dotnet restore @restoreArgs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Restore failed"
|
||||
exit 1
|
||||
}
|
||||
$buildArgs = @($generatorProj, '-c', 'Release', '--nologo', '-v', 'q', "-p:TargetFramework=$targetFramework", '--no-restore')
|
||||
if ($sdkVersionProp) { $buildArgs += $sdkVersionProp }
|
||||
dotnet build @buildArgs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Build failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Run the built executable directly (avoids dotnet run target framework mismatch issues)
|
||||
$generatorDir = Join-Path $PSScriptRoot 'cache-generator'
|
||||
$exePath = Join-Path $generatorDir "bin\Release\$targetFramework\CacheGenerator.exe"
|
||||
if (-not (Test-Path $exePath)) {
|
||||
# Fallback: try dll with dotnet
|
||||
$dllPath = Join-Path $generatorDir "bin\Release\$targetFramework\CacheGenerator.dll"
|
||||
if (Test-Path $dllPath) {
|
||||
$exePath = $null
|
||||
} else {
|
||||
Write-Error "Built executable not found at: $exePath"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
$runArgs = @()
|
||||
if ($Scan) {
|
||||
$runArgs += '--scan'
|
||||
}
|
||||
|
||||
# Detect installed WinAppSDK runtime via Get-AppxPackage (the WindowsApps
|
||||
# folder is ACL-restricted so C# cannot enumerate it directly).
|
||||
# WinMD files are architecture-independent metadata, so pick whichever arch
|
||||
# matches the current OS to ensure the package is present.
|
||||
$osArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString()
|
||||
$runtimePkg = Get-AppxPackage -Name 'Microsoft.WindowsAppRuntime.*' -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -notmatch 'CBS' -and $_.Architecture -eq $osArch } |
|
||||
Sort-Object -Property Version -Descending |
|
||||
Select-Object -First 1
|
||||
if ($runtimePkg -and $runtimePkg.InstallLocation -and (Test-Path $runtimePkg.InstallLocation)) {
|
||||
Write-Host "Detected WinAppSDK runtime: $($runtimePkg.Name) v$($runtimePkg.Version)" -ForegroundColor Cyan
|
||||
$runArgs += '--winappsdk-runtime'
|
||||
$runArgs += $runtimePkg.InstallLocation
|
||||
}
|
||||
|
||||
$runArgs += $ProjectDir
|
||||
$runArgs += $OutputDir
|
||||
|
||||
Write-Host "Exporting WinMD cache..." -ForegroundColor Cyan
|
||||
if ($exePath) {
|
||||
& $exePath @runArgs
|
||||
} else {
|
||||
dotnet $dllPath @runArgs
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Cache export failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Cache updated at: $OutputDir" -ForegroundColor Green
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<!-- Default fallback; Update-WinMdCache.ps1 overrides via -p:TargetFramework=net{X}.0 -->
|
||||
<TargetFramework Condition="'$(TargetFramework)' == ''">net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<!-- System.Reflection.Metadata is inbox in net9.0+, only needed for net8.0 -->
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="System.Reflection.Metadata" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Baseline WinAppSDK packages: downloaded during restore so the cache generator
|
||||
can always index WinAppSDK APIs, even if the target project hasn't been restored.
|
||||
ExcludeAssets="all" means they're downloaded but don't affect this tool's build.
|
||||
|
||||
When the repo has a known version (passed via -p:WinAppSdkVersion=X.Y.Z from
|
||||
Update-WinMdCache.ps1), prefer that version to avoid unnecessary NuGet downloads.
|
||||
Falls back to Version="*" (latest) on fresh clones with no restore.
|
||||
-->
|
||||
<ItemGroup Condition="'$(WinAppSdkVersion)' != ''">
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="$(WinAppSdkVersion)" ExcludeAssets="all" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(WinAppSdkVersion)' == ''">
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="*" ExcludeAssets="all" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,3 @@
|
||||
<Project>
|
||||
<!-- Isolate this standalone tool from the repo-level build configuration -->
|
||||
</Project>
|
||||
@@ -0,0 +1,3 @@
|
||||
<Project>
|
||||
<!-- Isolate this standalone tool from the repo-level build targets -->
|
||||
</Project>
|
||||
@@ -0,0 +1,3 @@
|
||||
<Project>
|
||||
<!-- Isolate this standalone tool from the repo-level Central Package Management -->
|
||||
</Project>
|
||||
1222
skills/winmd-api-search/scripts/cache-generator/Program.cs
Normal file
1222
skills/winmd-api-search/scripts/cache-generator/Program.cs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user