Using GitFlow and SourceTree for PowerShell Module Development
There are several ways on how to create a PowerShell (script) module. And depending on your environment chances are high you are not the only developer contributing to a module. […]
Audit and Consulting of Information Systems and Business Processes
There are several ways on how to create a PowerShell (script) module. And depending on your environment chances are high you are not the only developer contributing to a module. […]
There are several ways on how to create a PowerShell (script) module. And depending on your environment chances are high you are not the only developer contributing to a module. And if you care about versioning that is a little bit more sophisticated than just creating directories with semi-sequential version numbers this blog post might be something for you.
I will describe how you can use the ‘Feature Branch GitFlow’ from SourceTree in combination with manifest based NestedModules to facilitate PowerShell module developing. Here is a brief overview of what is covered:
For this article to follow you would need the following tools and resources
As written before PowerShell modules can be created in different ways. Here we will only focus on PowerShell script based modules (as opposed to C# based modules). There you basically have two choices:
module
with all script code in a large PSM1
filemanifest based module
with a PSD1
file describing the module and the actual functions and Cmdlets in separate source files.With a PSD1
you have much more fine-grained control over how your module is versioned and what will essentially be exported and advertised. For more information on module manifests see How to Write a Module Manifest.
As you can see from the following listing the manifest
based module biz.dfch.PS.System.Utilities
has defined a version number and NestedModule
(in contrast to the PSM1 based module biz.dfch.PS.Rapid.Utilities
):
PS > Get-Module ModuleType Name ExportedCommands ---------- ---- ---------------- Script biz.dfch.PS.Rapid.Utilities {ConvertFrom-Epoch, ConvertTo-Epoch, Get-RapidBillingPeriod, Get-Bill... Script biz.dfch.PS.System.Logging {Out-Message, Out-MessageAlert, Out-MessageCritical, Out-MessageDebug... Script biz.dfch.PS.System.Utilities {Assert-CmdletDocumentation, ConvertFrom-Base64, ConvertFrom-CmdletHe... Manifest Microsoft.PowerShell.Management {Add-Computer, Add-Content, Checkpoint-Computer, Clear-Content...} Manifest Microsoft.PowerShell.Utility {Add-Member, Add-Type, Clear-Variable, Compare-Object...} PS > Get-Module biz.dfch.PS.System.Utilities | fl Name : biz.dfch.PS.System.Utilities Path : C:\data\PSModules\biz.dfch.PS.System.Utilities\biz.dfch.PS.System.Utilities.psm1 Description : ModuleType : Script Version : 1.0.6.20150315 NestedModules : {New-CustomErrorRecord, Format-Xml, ConvertFrom-UnicodeHexEncoding, ConvertFrom-SecureStringDF...} ExportedFunctions : {Assert-CmdletDocumentation, ConvertFrom-Base64, ConvertFrom-CmdletHelp, ConvertFrom-Hashtable...} ExportedCmdlets : ExportedVariables : biz_dfch_PS_System_Utilities ExportedAliases : {ConvertFrom-ExchangeEncoding, fx} PS > Get-Module biz.dfch.PS.Rapid.Utilities | fl Name : biz.dfch.PS.Rapid.Utilities Path : C:\data\PSModules\biz.dfch.PS.Rapid.Utilities\biz.dfch.PS.Rapid.Utilities.psm1 Description : ModuleType : Script Version : 0.0 NestedModules : {} ExportedFunctions : {ConvertFrom-Epoch, ConvertTo-Epoch, Get-RapidBillingPeriod} ExportedCmdlets : ExportedVariables : biz_dfch_PS_Rapid_Utilities ExportedAliases : Get-BillingPeriod
To facilitate module developement we will have our exported Cmdlets and functions (or groups thereof) defined in separate PS1
files. So with biz.dfch.PS.System.Utilities
as an example the file structure would look like this (every exported Cmdlet is defined in a single PS1 file with the same name):
# script file that will be executed prior to module loading Import-Module.ps1 # manifest and description of module biz.dfch.PS.System.Utilities.psd1 # main module file used for module initialisation biz.dfch.PS.System.Utilities.psm1 # module configuration file biz.dfch.PS.System.Utilities.xml # license and general readme LICENSE NOTICE README.md # script files ConvertFrom-Base64.ps1 # contains ConvertFrom-Base64 Cmdlet ConvertFrom-CmdletHelp.ps1 # contains ConvertFrom-CmdletHelp Cmdlet ConvertFrom-Hashtable.ps1 # contains ConvertFrom-Hashtable Cmdlet # more script files ...
With this approach we can easily work on the same module ond separate Cmdlets (and thus files) with multiple developers. See the GitHub biz.dfch.PS.System.Utilities repository for the actual files and their contents (or check the sameple repository biz.dfch.PS.Testing.Example1 I created for this blog post).
Git defines several workflows that facilitate developement (based on Vincent Driessen’ article A successful Git branching model). Of course it is always a personal preference but for developing separate Cmdlets the Git Feature Branch Workflow seems quite well suited.
This means you will create a feature branch for each Cmdlet (or group of Cmdlets) we create and commit that to the development
branch via an intermediate feature
branch. As we have divided the code in separate files the only shared file is the PSD1
file where we have to adjust NestedModules
and possibly a few settings like FileList
and RequiredAssemblies
.
Every time we have finished a feature we can simply commit this to the development
branch (and optionally create a release
from it that will eventually pushed to the master
branch).
With these two approaches in mind we can now create a new PowerShell module and show with an example on how to create a new Cmdlet via a feature
branch that we will later merge into the development
branch and create a release
from it.
We will call our repository biz.dfch.PS.Testing.Example1
and will host it as public repository on GitHub at dfch / biz.dfch.PS.Testing.Example1. We can create the repository directly from within SourceTree or via the web interface:
Note: certain aspects of the repository can only be edited via the web interface. So you can also create the repository via the web interface and then clone it from within SourceTree to you local disk.
All GitFlow actions and commands are available under the menu ‘Repository / GitFlow’:
From there we will select Initialise repository
and use the default options:
In case we get an error message as shown below, this means that there is no master
branch yet in our repository (which can happen if we just created an empty repository):
To circumvent this we will commit
and push
an empty project with some default documents to the master
branch:
After retrying the repository initialisation we will end up with a message similar to this (as you can see we can always specify all Git command manually from the console):
To actually create a feature we start another GitFlow command Start Feature
. The name of the feature can nearly be anything, but it should be concise and still be descriptive:
After creating the feature we will notice a new branch Test-AnotherCmdlet
in our SoruceTree view:
Our feature will be a Cmdlet called Test-AnotherCmdlet
that will echo all input back to the console. This Cmdlet will be placed into the file Test-AnotherCmdlet.ps1
.
After having edited the feature we should commit
and push
the feature to the feature branch (not development
or master
), so other developers would have a chance to view or work on it:
After the push
the web view should look similar to this:
When the feature is finished, we invoke another GitFlow command Finish Feature
(or of course we could start another feature in parallel):
Finishing the feature means, we will merge it with the develop
branch:
So essentially we only added our new source file Test-AnotherCmdlet.ps1
and updated the PSD1
file as you can see from our commit 192fcb8. Other developers could have been adding features at the same time without worrying too much about collisions (except for the shared PSD1
file which could always easily be merged).
Now everything we developed is in the develop
branch waiting to be ‘released’. This means we start another GitFlow to create a release branch (1.0.1
in our case):
We can still commit something to this branch, such as an updated readme file:
Once we are happy with our release we finish it by invoking the respective GitFlow workflow:
This means we will merge the release branch with the develop
and master
branch. This can also be viewed on the web repository where you will see we now have a release 1.0.1
(that is currently the same as the master
and development
branch):
As we also defined a tag
we can always reference this point in time and look it up ander ‘Releases’ and ‘Tags’ in the web repository:
As you can see it is quite easy to bring PowerShell module development to the ‘next level’ (even it is rather a scripting language and often considered not real programming). Especially when using PowerShell scripts for more critical tasks it will pay off to have a structured developement approach in place that lets you up- and downgrade your modules in a safe and predictable manner.
This approach can also be combined with more formal releases such as NuGet packages as I described in PowerShell modules, digital signatures, nuspec files and packages automated.
To get a quick overview about Git workflows check the Smart branching with SourceTree and Git-flow or git-flow cheatsheet created by Daniel Kummer (based on the branching model by Vincent Driessen).