1[CmdletBinding(DefaultParameterSetName='Path')]
2param (
3 [Parameter(ParameterSetName='Path', Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
4 [string[]] $Path,
5
6 [Parameter(ParameterSetName='LiteralPath', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
7 [Alias('PSPath')]
8 [string[]] $LiteralPath,
9
10 [Parameter()]
11 [Alias('IncludeParents')]
12 [switch] $AllowParentProducts,
13
14 [Parameter()]
15 [switch] $PassThru,
16
17 [Parameter()]
18 [switch] $Force
19)
20
21process {
22 if ($PSCmdlet.ParameterSetName -eq 'Path') {
23 $LiteralPath = Resolve-Path $Path
24 }
25
26 foreach ($p in $LiteralPath) {
27 $file = Get-Item -LiteralPath $p
28 if (!$Force -and @('.md', '.markdown') -notcontains $file.Extension) {
29 Write-Verbose "Skipping $($file.FullName): does not appear to be a valid markdown file"
30 continue
31 }
32
33 [string[]] $content = $file | Get-Content
34 if (!$content -or !$content[0].StartsWith('---')) {
35 Write-Verbose "Skipping $($file.FullName): does not contain frontmatter"
36 continue
37 }
38
39 Write-Verbose "Checking $($file.FullName)"
40
41 # Reset metadata and create mutable collections.
42 $products = [System.Collections.Generic.List[string]]::new()
43
44 $i = 1
45 do {
46 [string] $line = $content[$i++]
47 if ($line.StartsWith('---')) {
48 break
49 }
50
51 # For each interesting section, set $current to a [list[string]].
52 if ($line -match '^\s*products:') {
53 $current = $products
54 $has_current = $true
55 }
56 elseif ($has_current -and $line -match '^\s*-\s+([\w-]+)') {
57 $current.Add($matches[1])
58 }
59 elseif (![string]::IsNullOrWhiteSpace($line)) {
60 $current = $null
61 $has_current = $false
62 }
63 } while ($i -lt $content.Length)
64
65 $has_errors = $false
66 $invalidProducts = @()
67
68 foreach ($product in $products) {
69 if ($productSlugs -notcontains $product) {
70
71 $has_errors = $true
72 $invalidProducts += $product
73
74 Write-Error "File '$($file.FullName)' contains invalid product slug: $product" -TargetObject $file `
75 -Category InvalidData -CategoryTargetName $product -CategoryTargetType string `
76 -RecommendedAction 'Use only product slugs listed at https://review.docs.microsoft.com/help/contribute/metadata-taxonomies?branch=master#product'
77 }
78 }
79
80 if ($has_errors) {
81 if ($PassThru) {
82 $file | Add-Member -PassThru -Type NoteProperty -Name InvalidProducts -Value $invalidProducts `
83 | Add-Member -PassThru -Type PropertySet -Name SampleMetadata -Value @('InvalidProducts')
84 }
85
86 $script_has_errors = $true
87 }
88 }
89}
90
91end {
92 if ($script_has_errors) {
93 exit 1
94 }
95}
96
97begin {
98 # https://review.docs.microsoft.com/help/contribute/metadata-taxonomies?branch=master#product
99 $productSlugs = @(
100 "ai-builder",
101 "aspnet",
102 "aspnet-core",
103 "azure-active-directory",
104 "azure-active-directory-b2c",
105 "azure-active-directory-domain",
106 "azure-advisor",
107 "azure-analysis-services",
108 "azure-anomaly-detector",
109 "azure-api-apps",
110 "azure-api-fhir",
111 "azure-api-management",
112 "azure-app-configuration",
113 "azure-app-service",
114 "azure-app-service-mobile",
115 "azure-app-service-static",
116 "azure-app-service-web",
117 "azure-application-gateway",
118 "azure-application-insights",
119 "azure-arc",
120 "azure-archive-storage",
121 "azure-artifacts",
122 "azure-attestation",
123 "azure-automation",
124 "azure-avere-vFXT",
125 "azure-backup",
126 "azure-bastion",
127 "azure-batch",
128 "azure-bing-autosuggest",
129 "azure-bing-custom",
130 "azure-bing-entity",
131 "azure-bing-image",
132 "azure-bing-news",
133 "azure-bing-spellcheck",
134 "azure-bing-video",
135 "azure-bing-visual",
136 "azure-bing-web",
137 "azure-blob-storage",
138 "azure-blockchain-service",
139 "azure-blockchain-tokens",
140 "azure-blockchain-workbench",
141 "azure-blueprints",
142 "azure-boards",
143 "azure-bot-service",
144 "azure-cache-redis",
145 "azure-cdn",
146 "azure-clis",
147 "azure-cloud-services",
148 "azure-cloud-shell",
149 "azure-cognitive-search",
150 "azure-cognitive-services",
151 "azure-communication-services",
152 "azure-computer-vision",
153 "azure-container-instances",
154 "azure-container-registry",
155 "azure-content-moderator",
156 "azure-content-protection",
157 "azure-cosmos-db",
158 "azure-cost-management",
159 "azure-custom-vision",
160 "azure-cyclecloud",
161 "azure-data-box-family",
162 "azure-data-catalog",
163 "azure-data-explorer",
164 "azure-data-factory",
165 "azure-data-lake",
166 "azure-data-lake-analytics",
167 "azure-data-lake-gen1",
168 "azure-data-lake-gen2",
169 "azure-data-lake-storage",
170 "azure-data-science-vm",
171 "azure-data-share",
172 "azure-database-mariadb",
173 "azure-database-migration",
174 "azure-database-mysql",
175 "azure-database-postgresql",
176 "azure-databricks",
177 "azure-ddos-protection",
178 "azure-dedicated-host",
179 "azure-dedicated-hsm",
180 "azure-dev-spaces",
181 "azure-dev-tool-integrations",
182 "azure-devops",
183 "azure-devops-tool-integrations",
184 "azure-devtest-labs",
185 "azure-digital-twins",
186 "azure-disk-encryption",
187 "azure-disk-storage",
188 "azure-dns",
189 "azure-encoding",
190 "azure-event-grid",
191 "azure-event-hubs",
192 "azure-expressroute",
193 "azure-face",
194 "azure-farmbeats",
195 "azure-files",
196 "azure-firewall",
197 "azure-firewall-manager",
198 "azure-form-recognizer",
199 "azure-front-door",
200 "azure-functions",
201 "azure-fxt-edge-filer",
202 "azure-genomics",
203 "azure-hdinsight",
204 "azure-hdinsight-rserver",
205 "azure-hpc-cache",
206 "azure-immersive-reader",
207 "azure-information-protection",
208 "azure-ink-recognizer",
209 "azure-internet-analyzer",
210 "azure-iot",
211 "azure-iot-central",
212 "azure-iot-dps",
213 "azure-iot-edge",
214 "azure-iot-hub",
215 "azure-iot-pnp",
216 "azure-iot-sdk",
217 "azure-iot-security-center",
218 "azure-iot-solution-accelerators",
219 "azure-key-vault",
220 "azure-kinect-dk",
221 "azure-kubernetes-service",
222 "azure-lab-services",
223 "azure-language-understanding",
224 "azure-language-service",
225 "azure-lighthouse",
226 "azure-linux-vm",
227 "azure-live-ondemand-streaming",
228 "azure-live-video-analytics",
229 "azure-load-balancer",
230 "azure-log-analytics",
231 "azure-logic-apps",
232 "azure-machine-learning",
233 "azure-machine-learning-designer",
234 "azure-machine-learning-studio",
235 "azure-managed-applications",
236 "azure-managed-disks",
237 "azure-maps",
238 "azure-media-analytics",
239 "azure-media-player",
240 "azure-media-services",
241 "azure-metrics-advisor",
242 "azure-migrate",
243 "azure-monitor",
244 "azure-netapp-files",
245 "azure-network-watcher",
246 "azure-notebooks",
247 "azure-notification-hubs",
248 "azure-open-datasets",
249 "azure-personalizer",
250 "azure-pipelines",
251 "azure-playfab",
252 "azure-policy",
253 "azure-portal",
254 "azure-powerbi-embedded",
255 "azure-private-link",
256 "azure-qio",
257 "azure-qna-maker",
258 "azure-quantum",
259 "azure-queue-storage",
260 "azure-rbac",
261 "azure-redhat-openshift",
262 "azure-remote-rendering",
263 "azure-repos",
264 "azure-resource-graph",
265 "azure-resource-manager",
266 "azure-rtos",
267 "azure-sap",
268 "azure-scheduler",
269 "azure-sdks",
270 "azure-search",
271 "azure-security-center",
272 "azure-sentinel",
273 "azure-service-bus",
274 "azure-service-fabric",
275 "azure-service-health",
276 "azure-signalr-service",
277 "azure-site-recovery",
278 "azure-sovereign-china",
279 "azure-sovereign-germany",
280 "azure-sovereign-us",
281 "azure-spatial-anchors",
282 "azure-speaker-recognition",
283 "azure-speech",
284 "azure-speech-text",
285 "azure-speech-translation",
286 "azure-sphere",
287 "azure-spring-cloud",
288 "azure-sql-database",
289 "azure-sql-edge",
290 "azure-sql-managed-instance",
291 "azure-sql-virtual-machines",
292 "azure-sqlserver-stretchdb",
293 "azure-sqlserver-vm",
294 "azure-stack",
295 "azure-stack-edge",
296 "azure-stack-hci",
297 "azure-stack-hub",
298 "azure-storage",
299 "azure-storage-accounts",
300 "azure-storage-explorer",
301 "azure-storsimple",
302 "azure-stream-analytics",
303 "azure-synapse-analytics",
304 "azure-table-storage",
305 "azure-test-plans",
306 "azure-text-analytics",
307 "azure-text-speech",
308 "azure-time-series-insights",
309 "azure-traffic-manager",
310 "azure-translator",
311 "azure-translator-speech",
312 "azure-translator-text",
313 "azure-video-indexer",
314 "azure-virtual-machines",
315 "azure-virtual-machines-windows",
316 "azure-virtual-network",
317 "azure-virtual-wan",
318 "azure-vm-scalesets",
319 "azure-vmware-solution",
320 "azure-vpn-gateway",
321 "azure-web-application-firewall",
322 "azure-web-apps",
323 "azure-webapp-containers",
324 "blazor-server",
325 "blazor-webassembly",
326 "common-data-service",
327 "customer-voice",
328 "dotnet-core",
329 "dotnet-standard",
330 "dynamics-business-central",
331 "dynamics-commerce",
332 "dynamics-cust-insights",
333 "dynamics-cust-svc-insights",
334 "dynamics-customer-engagement",
335 "dynamics-customer-service",
336 "dynamics-field-service",
337 "dynamics-finance",
338 "dynamics-finance-operations",
339 "dynamics-fraud-protection",
340 "dynamics-guides",
341 "dynamics-human-resources",
342 "dynamics-layout",
343 "dynamics-market-insights",
344 "dynamics-marketing",
345 "dynamics-prod-visualize",
346 "dynamics-product-insights",
347 "dynamics-project-operations",
348 "dynamics-project-service",
349 "dynamics-remote-assist",
350 "dynamics-retail",
351 "dynamics-sales",
352 "dynamics-sales-insights",
353 "dynamics-scm",
354 "dynamics-talent",
355 "dynamics-talent-attract",
356 "dynamics-talent-core",
357 "dynamics-talent-onboard",
358 "ef-core",
359 "ef6",
360 "expression-studio",
361 "m365-ems",
362 "m365-ems-cloud-app-security",
363 "m365-ems-configuration-manager",
364 "m365-information-protection",
365 "m365-myanalytics",
366 "m365-security-center",
367 "m365-security-score",
368 "m365-threat-protection",
369 "m365-workplace-analytics",
370 "mem-configuration-manager",
371 "mem-intune",
372 "microsoft-identity-web",
373 "mlnet",
374 "msal-android",
375 "msal-angular",
376 "msal-ios",
377 "msal-java",
378 "msal-js",
379 "msal-node",
380 "msal-python",
381 "msc-operations-manager",
382 "msc-service-manager",
383 "mscloud-financial",
384 "mscloud-healthcare",
385 "mscloud-manufacturing",
386 "mscloud-nonprofit",
387 "mscloud-retail",
388 "office-365-atp",
389 "office-access",
390 "office-adaptive-cards",
391 "office-add-ins",
392 "office-bookings",
393 "office-excel",
394 "office-exchange-server",
395 "office-forefront",
396 "office-kaizala",
397 "office-lync-server",
398 "office-onedrive",
399 "office-onenote",
400 "office-outlook",
401 "office-planner",
402 "office-powerpoint",
403 "office-project",
404 "office-project-server",
405 "office-publisher",
406 "office-skype-business",
407 "office-sp",
408 "office-sp-designer",
409 "office-sp-framework",
410 "office-sp-server",
411 "office-ui-fabric",
412 "office-visio",
413 "office-word",
414 "office-yammer",
415 "passport-azure-ad",
416 "power-apps",
417 "power-automate",
418 "power-bi",
419 "power-query",
420 "power-virtual-agents",
421 "return-to-school",
422 "return-to-workplace",
423 "sql-server-2008",
424 "surface-duo",
425 "sway",
426 "vs-app-center",
427 "vs-code",
428 "vs-mac",
429 "vs-online",
430 "windows-api-win32",
431 "windows-azure-pack",
432 "windows-forms",
433 "windows-iot",
434 "windows-iot-10core",
435 "windows-mdop",
436 "windows-mixed-reality",
437 "windows-server",
438 "windows-smb-server",
439 "windows-system-center",
440 "windows-uwp",
441 "windows-virtual-desktop",
442 "windows-wdk",
443 "windows-wpf",
444 "xamarin"
445 )
446
447 if ($AllowParentProducts) {
448 $productSlugs += @(
449 "azure",
450 "bing",
451 "blazor",
452 "connected-services-framework",
453 "consumer",
454 "customer-care-framework",
455 "dotnet",
456 "dynamics",
457 "dynamics-365",
458 "expression",
459 "flipgrid",
460 "github",
461 "hololens",
462 "industry-solutions",
463 "internet-explorer",
464 "kinect",
465 "m365",
466 "makecode",
467 "mdatp",
468 "mem",
469 "microsoft-authentication-library",
470 "microsoft-edge",
471 "microsoft-mesh",
472 "microsoft-servers",
473 "minecraft",
474 "mrtk",
475 "ms-graph",
476 "msc",
477 "office",
478 "office-365",
479 "office-teams",
480 "power-platform",
481 "project-acoustics",
482 "qdk",
483 "silverlight",
484 "skype",
485 "sql-server",
486 "surface",
487 "vs",
488 "windows",
489 "xbox"
490 )
491 }
492}
493
494<#
495.SYNOPSIS
496Checks sample markdown files' frontmatter for invalid information.
497
498.DESCRIPTION
499Given a collection of markdown files, their frontmatter - if present - is checked for invalid information, including:
500
501Invalid product slugs, i.e. those not listed in https://review.docs.microsoft.com/help/contribute/metadata-taxonomies?branch=master#product.
502
503.PARAMETER Path
504Specifies the path to an item to search. Wildcards are permitted.
505
506.PARAMETER LiteralPath
507Specifies the path to an item to search. Wildcards are not permitted.
508
509.PARAMETER AllowParentProducts
510Allow parent product slugs, like "azure" for "azure-key-vault".
511
512.PARAMETER PassThru
513By default, any invalid information is written to the $Error stream. Pass -PassThru to also return file items with error information attached.
514
515.PARAMETER Force
516Ignore file type validation.
517
518.EXAMPLE
519Get-ChildItem sdk -Filter *.md -Recurse | Test-SampleMetadata.ps1 -AllowParentProducts
520
521Searches all markdown (*.md) files under an "sdk" subdirectory for invalid frontmatter.
522
523.EXAMPLE
524Test-SampleMetadata.ps1 sample\README.md -PassThru | Select-Object FullName, SampleMetadata
525
526Shows sample metadata parsed and attached to the specified file object.
527
528.EXAMPLE
529Get-ChildItem sdk -Filter *.sample -Recurse | Test-SampleMetadata.ps1 -Force
530
531Searches for all .sample files and ignores file type validation within the script, which may lead to extraneous errors.
532#>
View as plain text