eng/common/scripts/Test-SampleMetadata.ps1 (516 lines of code) (raw):
[CmdletBinding(DefaultParameterSetName='Path')]
param (
[Parameter(ParameterSetName='Path', Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[string[]] $Path,
[Parameter(ParameterSetName='LiteralPath', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[Alias('PSPath')]
[string[]] $LiteralPath,
[Parameter()]
[Alias('IncludeParents')]
[switch] $AllowParentProducts,
[Parameter()]
[switch] $PassThru,
[Parameter()]
[switch] $Force
)
process {
if ($PSCmdlet.ParameterSetName -eq 'Path') {
$LiteralPath = Resolve-Path $Path
}
foreach ($p in $LiteralPath) {
$file = Get-Item -LiteralPath $p
if (!$Force -and @('.md', '.markdown') -notcontains $file.Extension) {
Write-Verbose "Skipping $($file.FullName): does not appear to be a valid markdown file"
continue
}
[string[]] $content = $file | Get-Content
if (!$content -or !$content[0].StartsWith('---')) {
Write-Verbose "Skipping $($file.FullName): does not contain frontmatter"
continue
}
Write-Verbose "Checking $($file.FullName)"
# Reset metadata and create mutable collections.
$products = [System.Collections.Generic.List[string]]::new()
$i = 1
do {
[string] $line = $content[$i++]
if ($line.StartsWith('---')) {
break
}
# For each interesting section, set $current to a [list[string]].
if ($line -match '^\s*products:') {
$current = $products
$has_current = $true
}
elseif ($has_current -and $line -match '^\s*-\s+([\w-]+)') {
$current.Add($matches[1])
}
elseif (![string]::IsNullOrWhiteSpace($line)) {
$current = $null
$has_current = $false
}
} while ($i -lt $content.Length)
$has_errors = $false
$invalidProducts = @()
foreach ($product in $products) {
if ($productSlugs -notcontains $product) {
$has_errors = $true
$invalidProducts += $product
Write-Error "File '$($file.FullName)' contains invalid product slug: $product" -TargetObject $file `
-Category InvalidData -CategoryTargetName $product -CategoryTargetType string `
-RecommendedAction 'Use only product slugs listed at https://taxonomy.learn.microsoft.com/TaxonomyServiceAdminPage/#/taxonomy/'
}
}
if ($has_errors) {
if ($PassThru) {
$file | Add-Member -PassThru -Type NoteProperty -Name InvalidProducts -Value $invalidProducts `
| Add-Member -PassThru -Type PropertySet -Name SampleMetadata -Value @('InvalidProducts')
}
$script_has_errors = $true
}
}
}
end {
if ($script_has_errors) {
exit 1
}
}
begin {
# https://taxonomy.learn.microsoft.com/TaxonomyServiceAdminPage/#/taxonomy/
$productSlugs = @(
"ai-builder",
"aspnet",
"aspnet-core",
"azure-advisor",
"azure-ai-content-safety",
"azure-analysis-services",
"azure-anomaly-detector",
"azure-api-apps",
"azure-api-fhir",
"azure-api-management",
"azure-app-configuration",
"azure-app-service",
"azure-app-service-mobile",
"azure-app-service-static",
"azure-app-service-web",
"azure-application-gateway",
"azure-application-insights",
"azure-arc",
"azure-archive-storage",
"azure-artifacts",
"azure-attestation",
"azure-automation",
"azure-avere-vFXT",
"azure-backup",
"azure-bastion",
"azure-batch",
"azure-bing-autosuggest",
"azure-bing-custom",
"azure-bing-entity",
"azure-bing-image",
"azure-bing-news",
"azure-bing-spellcheck",
"azure-bing-video",
"azure-bing-visual",
"azure-bing-web",
"azure-blob-storage",
"azure-blockchain-service",
"azure-blockchain-tokens",
"azure-blockchain-workbench",
"azure-blueprints",
"azure-boards",
"azure-bot-service",
"azure-cache-redis",
"azure-cdn",
"azure-clis",
"azure-cloud-services",
"azure-cloud-shell",
"azure-cognitive-search",
"azure-cognitive-services",
"azure-communication-services",
"azure-computer-vision",
"azure-container-instances",
"azure-container-registry",
"azure-content-moderator",
"azure-content-protection",
"azure-cosmos-db",
"azure-cost-management",
"azure-custom-vision",
"azure-cyclecloud",
"azure-data-box-family",
"azure-data-catalog",
"azure-data-explorer",
"azure-data-factory",
"azure-data-lake",
"azure-data-lake-analytics",
"azure-data-lake-gen1",
"azure-data-lake-gen2",
"azure-data-lake-storage",
"azure-data-science-vm",
"azure-data-share",
"azure-database-mariadb",
"azure-database-migration",
"azure-database-mysql",
"azure-database-postgresql",
"azure-databricks",
"azure-ddos-protection",
"azure-dedicated-host",
"azure-dedicated-hsm",
"azure-dev-spaces",
"azure-dev-tool-integrations",
"azure-devops",
"azure-devops-tool-integrations",
"azure-devtest-labs",
"azure-digital-twins",
"azure-disk-encryption",
"azure-disk-storage",
"azure-dns",
"azure-encoding",
"azure-event-grid",
"azure-event-hubs",
"azure-expressroute",
"azure-face",
"azure-farmbeats",
"azure-files",
"azure-firewall",
"azure-firewall-manager",
"azure-form-recognizer",
"azure-front-door",
"azure-functions",
"azure-fxt-edge-filer",
"azure-genomics",
"azure-hdinsight",
"azure-hdinsight-rserver",
"azure-health-data-services",
"azure-health-insights",
"azure-hpc-cache",
"azure-immersive-reader",
"azure-information-protection",
"azure-ink-recognizer",
"azure-internet-analyzer",
"azure-iot",
"azure-iot-central",
"azure-iot-dps",
"azure-iot-edge",
"azure-iot-hub",
"azure-iot-pnp",
"azure-iot-sdk",
"azure-iot-security-center",
"azure-iot-solution-accelerators",
"azure-key-vault",
"azure-kinect-dk",
"azure-kubernetes-service",
"azure-lab-services",
"azure-language-understanding",
"azure-language-service",
"azure-lighthouse",
"azure-linux-vm",
"azure-live-ondemand-streaming",
"azure-live-video-analytics",
"azure-load-balancer",
"azure-load-testing",
"azure-log-analytics",
"azure-logic-apps",
"azure-machine-learning",
"azure-machine-learning-designer",
"azure-machine-learning-studio",
"azure-managed-applications",
"azure-managed-disks",
"azure-maps",
"azure-media-analytics",
"azure-media-player",
"azure-media-services",
"azure-metrics-advisor",
"azure-migrate",
"azure-monitor",
"azure-netapp-files",
"azure-network-watcher",
"azure-notebooks",
"azure-notification-hubs",
"azure-openai",
"azure-open-datasets",
"azure-personalizer",
"azure-pipelines",
"azure-playfab",
"azure-policy",
"azure-portal",
"azure-powerbi-embedded",
"azure-private-link",
"azure-qio",
"azure-qna-maker",
"azure-quantum",
"azure-queue-storage",
"azure-rbac",
"azure-redhat-openshift",
"azure-remote-rendering",
"azure-repos",
"azure-resource-graph",
"azure-resource-manager",
"azure-rtos",
"azure-sap",
"azure-scheduler",
"azure-sdks",
"azure-search",
"azure-security-center",
"azure-sentinel",
"azure-service-bus",
"azure-service-fabric",
"azure-service-health",
"azure-signalr-service",
"azure-site-recovery",
"azure-sovereign-china",
"azure-sovereign-germany",
"azure-sovereign-us",
"azure-spatial-anchors",
"azure-speaker-recognition",
"azure-speech",
"azure-speech-text",
"azure-speech-translation",
"azure-sphere",
"azure-spring-cloud",
"azure-sql-database",
"azure-sql-edge",
"azure-sql-managed-instance",
"azure-sql-virtual-machines",
"azure-sqlserver-stretchdb",
"azure-sqlserver-vm",
"azure-stack",
"azure-stack-edge",
"azure-stack-hci",
"azure-stack-hub",
"azure-storage",
"azure-storage-accounts",
"azure-storage-explorer",
"azure-storsimple",
"azure-stream-analytics",
"azure-synapse-analytics",
"azure-table-storage",
"azure-test-plans",
"azure-text-analytics",
"azure-text-speech",
"azure-time-series-insights",
"azure-traffic-manager",
"azure-translator",
"azure-translator-speech",
"azure-translator-text",
"azure-video-indexer",
"azure-virtual-machines",
"azure-virtual-machines-windows",
"azure-virtual-network",
"azure-virtual-wan",
"azure-vm-scalesets",
"azure-vmware-solution",
"azure-vpn-gateway",
"azure-web-application-firewall",
"azure-web-apps",
"azure-web-pubsub",
"azure-webapp-containers",
"blazor-server",
"blazor-webassembly",
"common-data-service",
"customer-voice",
"dotnet-api",
"dotnet-core",
"dotnet-standard",
"document-intelligence",
"dynamics-business-central",
"dynamics-commerce",
"dynamics-cust-insights",
"dynamics-cust-svc-insights",
"dynamics-customer-engagement",
"dynamics-customer-service",
"dynamics-field-service",
"dynamics-finance",
"dynamics-finance-operations",
"dynamics-fraud-protection",
"dynamics-guides",
"dynamics-human-resources",
"dynamics-layout",
"dynamics-market-insights",
"dynamics-marketing",
"dynamics-prod-visualize",
"dynamics-product-insights",
"dynamics-project-operations",
"dynamics-project-service",
"dynamics-remote-assist",
"dynamics-retail",
"dynamics-sales",
"dynamics-sales-insights",
"dynamics-scm",
"dynamics-talent",
"dynamics-talent-attract",
"dynamics-talent-core",
"dynamics-talent-onboard",
"ef-core",
"ef6",
"entra",
"entra-external-id",
"entra-id",
"entra-id-protection",
"entra-identity-governance",
"entra-internet-access",
"entra-permissions-management",
"entra-private-access",
"entra-verified-id",
"entra-workload-identities",
"expression-studio",
"language-service",
"m365-ems",
"m365-ems-cloud-app-security",
"m365-ems-configuration-manager",
"m365-information-protection",
"m365-myanalytics",
"m365-security-center",
"m365-security-score",
"m365-threat-protection",
"m365-workplace-analytics",
"mem-configuration-manager",
"mem-intune",
"microsoft-identity-web",
"mlnet",
"msal-android",
"msal-angular",
"msal-ios",
"msal-java",
"msal-js",
"msal-node",
"msal-python",
"msc-operations-manager",
"msc-service-manager",
"mscloud-financial",
"mscloud-healthcare",
"mscloud-manufacturing",
"mscloud-nonprofit",
"mscloud-retail",
"office-365-atp",
"office-access",
"office-adaptive-cards",
"office-add-ins",
"office-bookings",
"office-excel",
"office-exchange-server",
"office-forefront",
"office-kaizala",
"office-lync-server",
"office-onedrive",
"office-onenote",
"office-outlook",
"office-planner",
"office-powerpoint",
"office-project",
"office-project-server",
"office-publisher",
"office-skype-business",
"office-sp",
"office-sp-designer",
"office-sp-framework",
"office-sp-server",
"office-ui-fabric",
"office-visio",
"office-word",
"office-yammer",
"passport-azure-ad",
"playwright",
"playwright-testing",
"power-apps",
"power-automate",
"power-bi",
"power-query",
"power-virtual-agents",
"return-to-school",
"return-to-workplace",
"sql-server-2008",
"surface-duo",
"sway",
"vs-app-center",
"vs-code",
"vs-mac",
"vs-online",
"windows-api-win32",
"windows-azure-pack",
"windows-forms",
"windows-iot",
"windows-iot-10core",
"windows-mdop",
"windows-mixed-reality",
"windows-server",
"windows-smb-server",
"windows-system-center",
"windows-uwp",
"windows-virtual-desktop",
"windows-wdk",
"windows-wpf",
"xamarin"
)
if ($AllowParentProducts) {
$productSlugs += @(
"azure",
"bing",
"blazor",
"connected-services-framework",
"consumer",
"customer-care-framework",
"dotnet",
"dynamics",
"dynamics-365",
"expression",
"flipgrid",
"github",
"hololens",
"industry-solutions",
"internet-explorer",
"kinect",
"m365",
"makecode",
"mdatp",
"mem",
"microsoft-authentication-library",
"microsoft-edge",
"microsoft-mesh",
"microsoft-servers",
"minecraft",
"mrtk",
"ms-graph",
"msc",
"office",
"office-365",
"office-teams",
"power-platform",
"project-acoustics",
"qdk",
"silverlight",
"skype",
"sql-server",
"surface",
"vs",
"windows",
"xbox"
)
}
}
<#
.SYNOPSIS
Checks sample markdown files' frontmatter for invalid information.
.DESCRIPTION
Given a collection of markdown files, their frontmatter - if present - is checked for invalid information, including:
Invalid product slugs, i.e. those not listed in https://taxonomy.learn.microsoft.com/TaxonomyServiceAdminPage/#/taxonomy/.
.PARAMETER Path
Specifies the path to an item to search. Wildcards are permitted.
.PARAMETER LiteralPath
Specifies the path to an item to search. Wildcards are not permitted.
.PARAMETER AllowParentProducts
Allow parent product slugs, like "azure" for "azure-key-vault".
.PARAMETER PassThru
By default, any invalid information is written to the $Error stream. Pass -PassThru to also return file items with error information attached.
.PARAMETER Force
Ignore file type validation.
.EXAMPLE
Get-ChildItem sdk -Filter *.md -Recurse | Test-SampleMetadata.ps1 -AllowParentProducts
Searches all markdown (*.md) files under an "sdk" subdirectory for invalid frontmatter.
.EXAMPLE
Test-SampleMetadata.ps1 sample\README.md -PassThru | Select-Object FullName, SampleMetadata
Shows sample metadata parsed and attached to the specified file object.
.EXAMPLE
Get-ChildItem sdk -Filter *.sample -Recurse | Test-SampleMetadata.ps1 -Force
Searches for all .sample files and ignores file type validation within the script, which may lead to extraneous errors.
#>