How to perform content freshness checks on your documentation files using PowerShell
Learn to use Pester and PowerShell for freshness

Let's face it. Every piece of documentation you write, whether it is a how-to, tutorial, or a long written guide, will be outdated at some point. Period.
AWS and Microsoft are large companies that follow a docs-as-code model. Each Markdown file committed to version control undergoes a content management policy to identify inactive content. If the content remains unmodified for an extended period, an alert is sent to the owners.
Most of those policies read the Frontmatter data for this information. That's why it's crucial to set boundaries and requirements for what is required in a Markdown file. This raises the question:
How can you do the same?
In this tutorial, you will learn:
- Why it's helpful to add Frontmatter data in your Markdown files.
- How to read the Frontmatter data.
- Perform testing using Pester to validate if a file hasn't been checked.
At the end of this tutorial, you know what key-value pairs you can add to your document to perform freshness checks for your precious documents.
Prerequisites
Before you begin this tutorial, you'll need the following:
- PowerShell 7+.
- Pester v5.7.1 or above.
Let's dive into it.
Adding Frontmatter data in Markdown
Frontmatter data deserves its place in the world of documentation. It's mainly used for static site generators. Documentation engines also rely on it to hide pages. For example, some engines like MkDocs will read the following data and not publish it on your website:
---
draft: true
---
<!-- Rest of your file content -->
Whenever you publish your document, it will be marked as a draft and not published instantly. This allows you to write your content upfront and possibly preview it on your website as well.
But what about the content itself?
You've spent countless hours writing something, and in three months, it'll be outdated. Can Frontmatter data be added and have a particular value? Certainly! Imagine the following data added at the top of your document:
---
owner: Gijs Reijn
last_reviewed_on: "06-20-2025"
---
<!-- my-super-hard-working-article.md -->
Two clearly defined key-value pairs stating the obvious. The document's owner is me, and it was last reviewed nearly three months ago. With this information specified in your document, you can create your content management policy and inform the document owner that it needs to be reviewed every 90 days.
You can recognize that defining such a policy entails a particular responsibility and overhead that the owner cannot escape. There's always a balance you need to seek and ask the relevant questions, like:
- Will we lose trust and credibility if the documentation doesn't
match the current product we've released? - Are outdated docs making it harder for new customers to adopt and onboard?
- How much time are internal teams wasting verifying or rewriting outdated documentation?
- How is stale documentation affecting our brand reputation?
But think about it.
A new customer comes in and wants to use your software. All the documentation was written years ago. Do you think the customer will be happy if they read the documentation, try an operation, and it fails instantly? The obvious:
A big fat no no.
Reading Frontmatter data
Now that you've learned how you can define valid Frontmatter data for a content management policy, it's time to look at how you can read it so you can later do something with it.
There have been folks already doing this in the .NET ecosystem for years. One of the most commonly used libraries is the Markdig library. Markdig allows you to parse raw Markdown and create objects that you can read. Imagine you've the following Markdown document:
---
owner: Gijs Reijn
last_reviewed_on: "06-20-2025"
---
<!-- how-to-read-markdown-data-using-markdig.md -->
# Introduction
<!-- Introduction text -->
## Using the library
<!-- Using the Markdig library -->
## Parse in PowerShell
<!-- Use PowerShell to parse -->
To install the Markdig library from NuGet.org, you can use the Install-Package
command:
- Open an elevated PowerShell session.
- Run
Install-Package Markdig -RequiredVersion 0.42.0
.
The package will be installed in your $env:ProgramFiles\NuGet\Packages\
directory. You can load the library by using the Add-Type
command:
Add-Type -Path "$env:ProgramFiles\PackageManagement\NuGet\Packages\Markdig.0.42.0\lib\netstandard2.0\Markdig.dll"
This exposes different types that you can use, allowing you to parse the Markdown:
# 1 create builder and call the extension as a static method
$builder = [Markdig.MarkdownPipelineBuilder]::new()
# call the extension method on the static helper class and reassign
$builder = [Markdig.MarkdownExtensions]::UseYamlFrontMatter($builder)
$pipeline = $builder.Build()
# 2 parse a file
$md = Get-Content 'how-to-read-markdown-data-using-markdig.md' -Raw
$doc = [Markdig.Markdown]::Parse($md, $pipeline)
# 3 find the YAML front-matter block
$yamlBlock = $null
foreach ($block in $doc) {
if ($block -is [Markdig.Extensions.Yaml.YamlFrontMatterBlock]) {
$yamlBlock = $block
break
}
}
You can clearly see the objects being created, and both key-value pairs are returned in two separate objects. Now, why is it helpful to read this data? Let's use this data and perform testing on it using Pester.
Perform documentation testing using Pester
With the data stored in variables, you can perform testing and fail whenever a condition is not met. In this case, the condition we've been discussing earlier is that you want to be notified when the last_reviewed_on
field needs a review.
Most open-source projects store documentation files in the docs
or documentation
directory. Take the .NET SDK repository for example:
For illustration purposes, you can use this repository and test if it's adhering to any rules:
-
Open a PowerShell terminal.
-
Clone the repository to your local machine:
git clone https://github.com/dotnet/sdk.git
-
In the root of the project, create a new file named
docs.tests.ps1
with the following content:[CmdletBinding()] param ( [System.String] $Path = "documentation", [System.Int32] $MaxDaysOld = 90 ) BeforeDiscovery { # Load Markdig library Add-Type -Path "$env:ProgramFiles\PackageManagement\NuGet\Packages\Markdig.0.42.0\lib\netstandard2.0\Markdig.dll" # Find all markdown files in the specified path $MarkdownFiles = Get-ChildItem -Path $Path -Filter "*.md" -Recurse | ForEach-Object { @{ Name = $_.Name FullName = $_.FullName RelativePath = $_.FullName.Replace((Get-Location).Path, "").TrimStart("\") } } } Describe "Markdown Documentation Tests" { Context "Metadata Validation for <Name>" -ForEach $MarkdownFiles { BeforeAll { # Create Markdig pipeline with YAML front-matter support $builder = [Markdig.MarkdownPipelineBuilder]::new() $builder = [Markdig.MarkdownExtensions]::UseYamlFrontMatter($builder) $pipeline = $builder.Build() # Parse the markdown file $md = Get-Content $FullName -Raw $doc = [Markdig.Markdown]::Parse($md, $pipeline) # Find the YAML front-matter block $yamlBlock = $null foreach ($block in $doc) { if ($block -is [Markdig.Extensions.Yaml.YamlFrontMatterBlock]) { $yamlBlock = $block break } } # Parse YAML metadata if found $metadata = @{} if ($yamlBlock) { $yamlLines = $yamlBlock.Lines foreach ($line in $yamlLines) { $lineText = $line.ToString() if ($lineText -match '^([^:]+):\s*"?([^"]*)"?$') { $key = $matches[1].Trim() $value = $matches[2].Trim() $metadata[$key] = $value } } } } It "Should have YAML front-matter block" { $yamlBlock | Should -Not -BeNullOrEmpty -Because "Every markdown file should have YAML front-matter with metadata" } It "Should have at least two metadata values" { $metadata.Count | Should -BeGreaterOrEqual 2 -Because "Each document should have at least two metadata fields" } It "Should have 'owner' metadata field" { $metadata.ContainsKey('owner') | Should -BeTrue -Because "Each document should specify an owner" $metadata['owner'] | Should -Not -BeNullOrEmpty -Because "Owner field should not be empty" } It "Should have 'last_reviewed_on' metadata field" { $metadata.ContainsKey('last_reviewed_on') | Should -BeTrue -Because "Each document should have a last reviewed date" $metadata['last_reviewed_on'] | Should -Not -BeNullOrEmpty -Because "Last reviewed date should not be empty" } It "Should be reviewed within three months from review date" { $dateString = $metadata['last_reviewed_on'] $lastReviewedDate = [datetime]::Parse($dateString) $daysSinceReview = ((Get-Date) - $lastReviewedDate).Days $daysSinceReview | Should -BeLessOrEqual $MaxDaysOld -Because "Document should be reviewed at least every three months ($MaxDaysOld days)" } } }
-
Execute
Invoke-Pester -Path docs.tests.ps1
in your PowerShell terminal.The project clearly lacks any content management policies in place. Let's do something about it and modify a file to see if the tests actually work.
-
Open the
migration-issues.md
file in your editor. -
At the top of the file, add the following Frontmatter data:
--- owner: "Me" last_reviewed_on: "05-19-2025" ---
-
Rerun the tests with the
-Output Detailed
added.
Note that four tests have passed, but the document still requires review. A clear indication that the written Pester tests are working successfully. You have now successfully tested a content freshness policy.
Summary
In this tutorial, you added meaningful Frontmatter (owner
, last_reviewed_on
) to a Markdown file. The Markdig library allowed you to parse the Frontmatter data easily and enforced a lightweight content freshness policy using Pester.
The tests validate that each document has metadata, an owner, and a last review date. The last test makes sure the review date doesn't exceed a specific window, in this case, 90 days.
This approach provides an auditable and automatable mechanism to identify stale documentation early on. It can be easily integrated into a CI/CD system. When a test fails, you can hit up the owner
of the document and ask for a review.