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...
ScriptRunner Blog
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.
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.
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.
I used to maintain changelogs like this:
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:
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
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
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
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
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
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?
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.
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.
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.
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.
Jan 28, 2025 by Jeffery Hicks
Changelogs keep your software updates clear and organized. Learn the best practices for creating and managing them in...
Dec 19, 2024 by Jeffery Hicks
Boost IT efficiency with Winget and PowerShell! Learn how to automate app installations, updates, and management...
Dec 17, 2024 by Sonny Jamwal
Extend PowerShell with .NET for powerful event automation. Learn how to monitor and handle system events like a pro!...
Jeffery Hicks is an IT veteran with almost 35 years of experience, much of it spent as an IT infrastructure consultant specializing in Microsoft server technologies with an emphasis on automation and efficiency. He is a multi-year recipient of the Microsoft MVP Award. Jeff is a respected and well-known author, teacher, and consultant. Jeff has taught and presented PowerShell content and the benefits of automation to IT Pros worldwide for the last 20 years. Jeff is a Pluralsight author and a frequent speaker at technology conferences and user groups. Keep up with Jeff at https://jdhitsolutions.github.io