Skip to the main content.

ScriptRunner Blog

Mastering Changelog Management with PowerShell

Table of Contents 

Post Featured Image

Changelogs keep your software updates clear and organized. Learn the best practices for creating and managing them in your IT workflows!  Let's talk about the purpose and benefits of changelog management.

 

Do you write PowerShell modules? Do you keep a change log? Do you know the best way to structure it? While there isn’t an absolute or universal change log standard, there are some best practices you can follow using the widely accepted Keep a Changelog format. In this article, I’ll show you how to manage your change log with PowerShell and the ChangeLogManagement module. I’ll also share some code that I use to help me manage my change logs and how I incorporate it into my PowerShell modules.

 

What is a ChangeLog

A change log is a record of changes to a project. It is a way to communicate to users what has changed in a new release. Not only does the changelog provide a history of the project, if you publish a PowerShell module with a changelog and something breaks, the log might help the user, and you, determine a likely cause. A good changelog will not only document what changed but why.

Change logs should be written for humans, not machines. They should be easy to read and understand. A change log should be written in a way that is meaningful to the user and clearly identifies what has changed. Typically, changelogs are written in Markdown so that you can reference issues and pull requests. In other words, the changelog can be an interactive document for you, or an end-user, that serves as historical record as well as a troubleshooting aid.

I’ll cover what should go into a changelog in a moment, but first, let’s talk about what a changelog isn’t.

 

What Isn’t a ChangeLog

A changelog is more than a list of recent git commits. Your git commit messages serve a different purpose. While you can, and some people do, write them so that they can be re-used in a changelog, that isn’t their primary purpose. What you commit to git is not necessarily a change that is meaningful to an end-user. Don’t get me wrong, there may be git commit messages that are great additions to a changelog. But don’t assume copying all your commit messages into a changelog is all you need to do.

A changelog is also not a list of every file that has changed since the last release. You will implicitly list what files have changed, but the changelog will include information about why and how.

 

The Keep a ChangeLog Model

I used to maintain changelogs like this:

Legacy Changelog

Legacy Changelog

 

Then I discovered the Keep a Changelog model. I encourage you to take a few minutes to visit the site and read the documentation. There’s no reason for me to duplicate here. But the key points are:

    • Organize changes by type: Added, Changed, Deprecated, Removed, Fixed, Security
    • Standardize on date format
    • Identify changes that might break something

The changelog will use semantic numbering for releases. If this is new to you, the format is MAJOR.MINOR.PATCH. The MAJOR version is incremented when you make significant changes to the project, or introduce a change that will likely break older installations. The MINOR version is incremented when you add functionality in a backwards-compatible manner. You might add a new command or a new parameter. The PATCH version is incremented when you make backwards-compatible bug fixes. Typically, when you increment a level, the lower levels are reset to zero. For example, if you increment the MINOR version, the PATCH version is reset to zero. At least, that is how i handle things.

Within each release you will have a subheading for the different types of changes. For example:


## [1.1.0] - 2024-08-01
### Added
- Added a new command `Get-Widget`
### Changed
- Changed the default behavior of `Get-Widget` to return all widgets.
- Updated about documentation

 

Using the ChangeLogManagement Module

To make this process easier, you can use the ChangeLogManagement module. You can install it from the PowerShell Gallery.


Install-Module -Name ChangeLogManagement

 

You will use the module commands to update your changelog and to create a new release. The module will help you maintain the structure and format of your changelog.


PS C:\work\PSMagic> Get-Command -module ChangelogManagement | Select Name
Name
----
Add-ChangelogData
ConvertFrom-Changelog
Get-ChangelogData
New-Changelog
Update-Changelog

 

Creating a new ChangeLog

Of course, you first need a changelog. If you are starting from scratch, change to the root of your project and run the New-ChangeLog command.

 



New-ChangeLog

You should be able to accept the defaults.

New-ChangeLog

New-ChangeLog

 

You can delete the notes at the beginning of the file. I also adjust the headingto include the name of my project.


# Changelog for PSMagic

 

Adding ChangeLog Data

When you need to add information to the changelog, you can always enter it manually. But I prefer to use the Add-ChangelogData command. You need to specify the type of change (Added, Changed, Deprecated, Removed, Fixed, or
Security) and a description.


Add-ChangelogData -Type Added -Data "Added support for ``WhatIf`` in ``Set-Foo``."
Add-ChangelogData -Type Added -Data "Added command ``Set-FooPreference`` with an alias of ``s
Add-ChangelogData -Type Changed -Data "Updated ``README``."

 

Because the changelog is using Markdown file, and I want to format the parameter and command name as inline code, I’m enclosing them in double backticks.

If you make a mistake, you can always edit the file manually.

Note: Make sure the Changelog file has been saved before running the Add-ChangelogData command.

Here’s what the changelog might look like:


4# Changelog for PSMagic
## [Unreleased]
### Added
- Added command `Set-FooPreference` with an alias of `sfp`.
- Added support for `WhatIf` in `Set-Foo`.
- Initial release
### Changed
- Updated `README`.

 

Because the changelog is structured, you can use Get-ChangelogData to treat the
changelog as an object.


PS C:\work\PSMagic> Get-ChangelogData
Header : # Changelog for PSMagic
Unreleased : @{RawData=## [Unreleased]
### Added
- Added command `Set-FooPreference` with an alias of `sfp`.
- Added support for `WhatIf` in `Set-Foo`.
- Initial release
### Changed
- Updated `README`.
; Link=; Data=; ChangeCount=4}
Released : {}
Footer :
LastVersion :
ReleaseNotes :

 

Let me demonstrate with a more complete changelog from one of my projects.


PS C:\Scripts\PSWorkItem> $d = Get-ChangelogData
PS C:\Scripts\PSWorkItem> $d.LastVersion
1.10.0
$dPS C:\Scripts\PSWorkItem> $d.Released | Select-Object Version,Date,ChangeCount -First 5
Version Date ChangeCount
------- ---- -----------
1.10.0 5/28/2024 12:00:00 AM 6
1.9.0 2/24/2024 12:00:00 AM 6
1.8.0 2/23/2024 12:00:00 AM 11
51.7.0 1/7/2024 12:00:00 AM 9
1.5.0 10/6/2023 12:00:00 AM 11

 

Updating the ChangeLog

When it comes time for a new release, you will need to update the changelog. This can be a little tricky at first. First, there is an assumption that your project is being managed with git and has a remote repository, usually on GitHub. The Update-Changelog command will create a new release in your changelog file based on the version number you specify.

The process will also create links to the GitHub repository and the compare view so that you can compare between releases. This assumes you are creating releases with appropriate tags. The Update-ChangeLog command uses the LinKMode parameter to specify the type of link to create. You will typically use Manual for local projects or those where you will manually fix the links later, or Automatic. There are options that support GitHub actions and Azure DevOps, but those are advanced and specialized usages I’m not going to get into.


PS C:\work\PSMagic> Update-Changelog -ReleaseVersion 0.1.0 -LinkMode Manual

 

Because you selected LinkMode Manual, you will need to manually update the links at the bottom of the output file.

Here’s the revised changelog.


# Changelog for PSMagic
## [Unreleased]
## [0.1.0] - 2024-09-19
### Added
- Added command `Set-FooPreference` with an alias of `sfp`.
- Added support for `WhatIf` in `Set-Foo`.
- Initial release
### Changed
- Updated `README`.
[Unreleased]: ENTER-URL-HERE
[0.1.0]: ENTER-URL-HERE

 

The ENTER-URL-HERE placeholders are where you will need to update the links. Or simply delete them.

If you set the LinkMode to Automatic, then you also need to specify a LinkPattern. This is a hashtable that defines the URL pattern for the links. You need to define links for FirstRelease, NormalRelease, andUnreleased. You can use placeholders of{CUR}for the current version and{PREV}‘ for the previous version. The help example is as good as anything:


$lp = @{
FirstRelease="https:))github.com/testuser/testrepo/tree/v{CUR}"
NormalRelease="https:))github.com/testuser/testrepo/compare/v{PREV}.)v{CUR}"
Unreleased="https:))github.com/testuser/testrepo/compare/v{CUR}.)HEAD"}
}
Update-Changelog -ReleaseVersion 1.1.1 -LinkMode Automatic -LinkPattern $lp

 

This example assumes your release tags include a prefix of v. You can always test the links from VSCode and adjust as needed. Take a look at one of my changelogs to see how all of this works. The release links will open the GitHub compare commit view. If you look at the raw markdown, you’ll see the URL link pattern at the end of the file.

Running Update-Changelog only creates the new release section. You still need to publish the release to GitHub or wherever you are hosting your project. However, when you get the changelog from the command line, the ReleaseNotes property will contain the new release information.


PS C:\work\PSMagic> $d = Get-ChangelogData
PS C:\work\PSMagic> $d.LastVersion
0.1.0
PS C:\work\PSMagic> $d.ReleaseNotes
### Added
- Added command `Set-FooPreference` with an alias of `sfp`.
- Added support for `WhatIf` in `Set-Foo`.
- Initial release
### Changed
- Updated `README`.

 

Are you beginning to see some of the scripting possibilities?

 

Migrating an Existing ChangeLog

What if you have an existing changelog? Depending on how you have it formatted, you might be able manually add the Unreleased section and manually insert the links. At the very least, you can begin to manually insert changes using the same headings like Added and Fixed. On new projects, use the ChangeLogManagement module from the beginning. I have a function that I use to convert my old changelogs into the new format.


Function ConvertTo-NewChangeLog {
<#
7.SYNOPSIS
Convert my old change logs to the semantic ChangeLog format
.DESCRIPTION
Convert old change logs to the new semantic ChangeLog format.
This will also insert Github compare links.
.EXAMPLE
PS C:\> ConvertTo-NewChangeLog | Set-Clipboard
#>
[CmdletBinding()]
[alias('ctnc')]
[OutputType('String')]
Param(
[Parameter(Position = 0)]
[ValidateScript({ Test-Path $_ })]
[ValidatePattern("\.md$")]
[string]$Path = 'Changelog.md'
)
$log = [System.Collections.Generic.list[string]]:)new()
Get-Content -Path $Path | ForEach-Object { $log.Add($_) }
$log.Insert(1, "`n## [Unreleased]")
[regex]$rx = '(?<=##\s)(v)?\d\.\d+\.\d'
$VersionList = [System.Collections.Generic.list[string]]:)new()
#Replace the matching version number in [ ]
$log.FindAll({ $args[0] -match $rx }) | ForEach-Object {
$ver = $rx.match($_).value
$VersionList.Add($ver)
$i = $log.IndexOf($_)
#get git commit date
$commitDate = git log --grep=$ver --pretty=format:'%cs' | Select-Object -Last 1
$r = $_ -replace $ver, "[$ver] - $CommitDate"
Write-Verbose "Replace index $i $_ with $r"
$log[$i] = $r
}
$GHPath = (git remote get-url --push (git remote)).Split('.git')[0]
$log.Add("`n[Unreleased]: $GHPath/compare/$($m[0].Value).)HEAD")
foreach ($version in $VersionList) {
$previous = $VersionList[$VersionList.IndexOf($version) + 1]
if ($previous) {
$link = "[$version]: $GHPath/compare/$previous.)$version"
$log.Add($link)
8}
}
$log
}

 


The function assumes your module is also a git repository with a remote GitHub repository. I use v as a prefix for my version numbers so you might need to make some changes. The function will generate a new changelog complete
with the version links. The function writes the new changelog to the console as text. You can copy it to the clipboard, send to a new file, or overwrite the old file. I usually have to tweak the links a bit, but overall this function saves me a lot of time.

 

My ChangeLog Practices

If you are updating code to fix a bug or problem, you should record the change as a Fixed type. If there is an associated issue, I recommend including a link. I have this function that I use in VSCode to insert a Markdown link to an issue.


Function New-GHIssueLink {
Param(
[Parameter(Position = 0, Mandatory)]
[string]$IssueNumber
)
$ModuleName = Get-Location | Split-Path -Leaf
#get current directory
$IssueText = '[Issue #)0}](https:))github.com/<YOUR-ORG-NAME-HERE>/{1}/issues/{0})' -f $I
$Context = $PSEditor.GetEditorContext()
$context.CurrentFile.InsertText($IssueText)
}

 

You will need to replace <YOUR-ORG-NAME-HERE> with your GitHub organization name. I have this function in my PowerShell profile so that I can easily insert a link to an issue.


PS C:\Scripts\MyTasks> New-GHIssueLink 11

 

The function will insert this Markdown text into the current file at the current
cursor location.


[Issue #11](https:))github.com/jdhitsolutions/MyTasks/issues/11)

 

I rarely need to reference pull requests, but you could easily modify the function to create a link to a pull request or even a discussion topic.

When it comes time for a new release, I build the release and push it from my desktop. I first run this script, PrepNewRelease.ps1 to update the changelog with a new release.


#requires -module ChangeLogManagement
#PrepNewRelease.ps1
#get module version from manifest
$moduleName = Split-Path $PSScriptRoot -Leaf
$data = Import-PowerShellDataFile .\$moduleName.psd1
$ver = $data.ModuleVersion
Write-Host "Building release for $moduleName version $ver" -ForegroundColor Cyan
Write-Host "Has help been updated? Are you ready to continue?" -ForegroundColor Cyan
Pause

#FirstRelease="https:))github.com/jdhitsolutions/$moduleName/tree/v{CUR}";
Update-Changelog -ReleaseVersion $ver -LinkMode Automatic -LinkPattern @{
NormalRelease="https:))github.com/jdhitsolutions/$moduleName/compare/v{PREV}.)v{CUR}";
Unreleased ="https:))github.com/jdhitsolutions/$moduleName/compare/v{CUR}.)HEAD"
}

#verify and touch-up change log
code $PSScriptRoot\changelog.md
$msg = @'
Next steps:
- Finalize change log
- Run New-ReleaseChangeLog.ps1
- Update ReleaseNotes in module manifest
$c = Get-ChangelogData
"## $($c.lastVersion)`n`n$($c.ReleaseNotes)" | Set-Clipboard
- git add .
- git commit -m "v<Version>"
- publish project to the PowerShell Gallery
- push release
'@
Write-Host $msg -foreground yellow

 

I always manually review the changelog and make last minute adjustments
such as adding blank lines between headings. Once the changelog is ready, I
run this script, New-ReleaseChangeLog.ps1.


10#requires -module ChangeLogManagement
#get ISO8601 date
$dt = Get-Date -format "yyyy-MM-dd HH:mm:ss"
$moduleName = Split-Path -path $PSScriptRoot -Leaf
#get changelog data
$change = Get-ChangelogData -Path .\ChangeLog.md
#get latest release
$modVersion = $change.LastVersion
#get previous releases
[regex]$rx = "(?<=\[)\d\.\d\.\d"
$releases = $change.footer.split("`n") | Select-Object -skip 1
$prev = $rx.match($releases[1]).value
$link = "[$modVersion]: https:))github.com/jdhitsolutions/$moduleName/compare/v$prev...))v$modV
#$change.footer.split("`n").where({$_ -match "\[$modVersion\]"})
if ($modVersion -as [version]) {
$md = [System.Collections.Generic.list[String]]:)new()
$md.add($change.header.split("`n")[0])
$md.add("## v[$modVersion] - $dt`n")
$md.add($change.ReleaseNotes)
$md.Add("`n$link")
$md | Out-File .\scratch-change.md
code .\scratch-change.md
}
else {
Write-Warning "Changelog does not follow the newer format."
}

 

I have a separate process for creating the release using the gh.exe command line tool. You can see that I am using the changelog data to build the release notes. You’ll also see that I have a reminder to get the release notes and update the module manifest.

 

Conclusion

Using a standardized changelog format can help you communicate changes to your users and help you troubleshoot problems. The ChangeLogManagement module can help you maintain your changelog and create new releases. I’ve
shared some of the code I use to help me manage my changelogs and I hope it sparked a few ideas on how you can improve your changelog management.

 

all of our PowerShell cheat sheets for you, incl. Active Directory, Graph, Teams, Exchange and more on Security

Your ultimate PowerShell Cheat Sheet

Unleash the full potential of PowerShell with our handy poster. Whether you're a beginner or a seasoned pro, this cheat sheet is designed to be your go-to resource for the most important and commonly used cmdlets.

The poster is available for download and in paper form.

PowerShell Poster 2023

Get your poster here!

 

 

Related links 


 

  •  

Related posts

13 min read

Mastering Changelog Management with PowerShell

Changelogs keep your software updates clear and organized. Learn the best practices for creating and managing them in...

14 min read

How to Use Winget and PowerShell for Efficient App Deployment

Boost IT efficiency with Winget and PowerShell! Learn how to automate app installations, updates, and management...

17 min read

How to Leverage .NET in PowerShell for event-driven scripting

Extend PowerShell with .NET for powerful event automation. Learn how to monitor and handle system events like a pro!...

About the author: