This documentation should help everybody who want to contribut to this project. Primarily it shall explain basic principals in the design of this module as well as development workflows and requirements.
How are project files organized and structured?
. PowerShell Module Project Root
|__ .github | all Github related files as workflows, issue or pull request templates
|__ .vscode | all shared VSCode and related settings, tasks and scripts (e.g. local git hooks)
|__ build | build tools (PSake properties, psakeFiles)
| |__ tasks | PSake build task includes
| |__ dependencies.psd1 | PSDepend build dependencies
| \__ psakeFile.{ENV}.ps1 | PSake build definitions (for each build environment)
|__ BuildOutput | default build output directory
|__ docs | static docs for manual reading (generated doc added by build task)
|__ src | all module code files
| |__ Private | all 'private' functions, helpers, ...
| | \... |
| |__ Public | all 'public', main functions
| | \... |
| |__ Connect-MSS65.psd1 | module manifest file
| \__ Connect-MSS65.psm1 | main module code
|__ tests | all Pester scripts
| |__ Connect-MS365.Tests.ps1
| \... |
|__ .gitignore | standard .gitingore
|__ build.ps1 | main build script used to start build tasks (psake)
|__ LICENSE | general license information
|__ README.md | general README info
\__ RELEASENOTES.md | Release history information
What should be ignored? ...and what shouldn't?
-
BuildOutput all build outputs should not be committed. If something is generated that needs to be used somewhere else, the build task has to care about pubishing this artifact to another location or system. This does not include 'build' as this is our default directory for all build-related tools.
-
IDE configuration some settings of your IDE configuration that might include personal information / local system specific things, e.g. .vscode/extensions.json .vscode/launch.json. What I shared is
tasks.json
as it may help others as well to have all common sense on IDE task configuration? I also added Git Hook scripts into .vscode (although it is not really vscode-specific, I know) for local Git environments to have consistency through all contributors.
No commits without message! NOT ONE!
We are talking about contributing, right? That means at least we are two persons that are working on the same code and might want to understand what the hack the other one was doing...
Commit messages are required!
- add issue number (#) reference in the beginning (if it is issue related); if many issues exist, please reference them all
- do more commits! it is always easier to have precise commit messages if you have more but smaller commits. I personally don't like huuuuge commits with messages like "added one very large module" (which contains 1254 changes, adds and deletes)
- push! as you should have a branch anyway (see below), there is no reason to delay your push (at least if some tested, working state is there). We want to participate from your work and may want to give feedback or take over if you don't want to continue with it.
- master our main developent tree, always the latest state in development
- feature/abc please branch your work for new features into a feature-branch and please use
feature/
-prefix.
Please sign your commits with your gpg key!
If you don't do this yet, here is the best Windows-related documentation about automatically signing git commits with Windows.
At the moment we don't use any local Git hooks. I tend to use Notary (vcn) service, but as this is too complicated and doesn't scale very good I disabled that.
Well...it's just a PowerShell module and I am not a coder myself...so what can I tell...?
Most important is:
- all module source stays in
src
directory, all other directories have general or other purposes - the main module
Connect-MS365.psm1
is just invokingConnect-<Service>
cmdlets again Connect-<Service>
cmdlets are provided by individual functions in single.ps1
-files insrc\Public
(although we don't export the functions)- all other functions (helpers, ...) should be realized in individual
.ps1
-files insrc\Private
(definetely not exported) - please respect and maintain Manifest file (
Connect-MS365.psd1
)!
In general I try to stick to some basic PowerShell module best-practices I liked a lot when reading them at Dr. Scripto and at powershell-guru.com. Another nice one can be found at PoshCode's GitHub.
Yes, I know...it seems that even my fingers try to ignore some of them. So if you find anything to improve into those best practices...please feel free 😁
- Don't use alias (write 'Set-Location' instead of 'cd', use )
- Don't rely on positional parameters (e.g. for Copy-Item we use -path and -destination)
- Use try-catch and some use the catch-part and use try{} for all actions considered as a transaction
- Avoid Out-Null cmdlet and redirecting to $null (for performance reasons). Better to cast to [void]
[void]$array.Add('A)
or assign to $null$null = $array.Add('A')
- Use single quotes for string (') instead of double quotes (") if not necessary, as they disable variable expansion and unexpected results are reduced
- Always Start With CmdletBinding and at least consider make use of its features as accepting pipelining.
- Write full sentences as inline comment documentation (for further details see below)
- We use 'PascalCase' for all names (variables, module, functions, attributes, classes, enum, fields, constants, ...).
- We use 'lowercase' for language keywords (foreach, operators, ...)
- Please use Verb-Noun for cmdlets, functions, etc. and avoid plural nouns.
- We use 'One True Brace Style variant by K&R': opening braces "{" are at end of line, closing braces "}" at the beginning of the line.
- 4 spaces are one indent
- limit outputs to max of 115 chars per line (e.g. PowerShell has default max of 120), consider use of "Splatting"
- Spaces around assignments, parameters and operators
$variable = Get-Content -Path $FilePath -Wait:($ReadCount -gt 0) -TotalCount ($ReadCount * 5)
We use Pester and PSScriptAnalyzer to have tests on our code.
There are the following three stages in testing:
-
Unit Tests
This are all tests that should be tested directly while developing and don't need any other components. They are executed by build task
PesterUnit
withInvoke-Pester -Tags Unit
. -
Integration Tests
This are all tests that should be tested after build process which creates some additional components as well. They are executed by build task
PesterUnit
withInvoke-Pester -Tags Integration
. -
Script Analyze
We use
PSScriptAnalyzer
to analyze our PowerShell code against some standard best practices. It is executed by build taskAnalyze
withInvoke-ScriptAnalyzer
.Warnings are not blocking, Errors are blocking other succeeding build tasks. Some warnings can not be solved, e.g. it is complaining about Connect-Teams using a plural term Team_S_ what should be avoided...well...yeah...
Well, I think here is a lot room to improve...
As I am not a developer I am not a testing specialist as well. At least I try to test the following parts:
- main module
- is available itself
- parameters of root module (Type, Mandatory, ...)
- valid PowerShell code
- manifest file available
- functions
- parameters of functions (using
InModuleScope
) - functions not beeing available directly (outside
InModuleScope
)
- parameters of functions (using
- builded module (integration)
- valid manifest file
- ExternalHelp XML is generated
- root docs files are available
Please maintain the documentation as well!
If you contribute please update our documentation. This consists at least of the following
-
Inline Documentation
We use inline PowerShell documentation to automatically generate help files and outputs for
Get-Help
cmdlet.- Module's main inline documentation stays outside the
function()
- Functions` inline documentations stay inside the
functon()
- add semantic descriptional written sentences to explain your code steps
This is a requirement by our automatic help generator...
- Module's main inline documentation stays outside the
-
Parameter Documentation
Please use inline documentation &
HelpMessage
for parameters:param ( # my inline comment for the $Module parameter [Parameter(Mandatory=$true,Position=1,HelpMessage="My Modules Helpmessage")] [String] $Module )
-
ReleaseNotes
Usually I have prepared
src\RELEASENOTES.md
with a skeleton for the next version when I have done a release. Just add some line toNew Features
,Changes
orFixes
. The line should be in the format of:- #<IssueId> description / title as in RELEASENOTES.md
-
docs*.md Files
Please maintain
.md
-files indocs
accordingly. If something changes in installation process:01-INSTALLATION.md
If you add / change functionality that is directly invoked by the user, please maintain02-USAGE.md
._There is no need to manually maintain
Connect-MS365.md
as it is automatically generated by our build process.
:info: We provide two ways of building our stuff:
-
manual compile, build, test with
build.ps1
This is primarily meant to be used within development before committing / pushing your code into the repository, you get similiar results as our CI would deliver later.
Just call
build.ps1 [-Task]<taskname>
to execute one of the build tasks. Build outputs are created inBuildOutput
-directory with<ModuleName>
subfolder.It supports the following parameters:
-
-Task <taskname>
The name of build task, defined in
psakeFile.ps1
. If dependencies are defined, depending tasks are executed first. -
-Bootstrap
This executes a bootstrap procedure and sets up all necessary modules. For details see below.
-
-Help
Displays Help and lists all defined build tasks
-
-
CI / CD with AppVeyor
We have an AppVeyor project for CI / CD pipelining.
It is building and testing on all branches, except for update of
*.md
-files and commit messages include something as
update readme.*|update readme.*s|update docs.*|update version.*|*readme*
.It is performing tests and creates an artifact zip-file
Connect-MS365-vX.Y.<buildnumber>.zip
.AppVeyor is configured in appveyor.xml.
Later: (TODO)
Deployments (see below) are triggered onmaster
-branch commits that don't have any PR number.
The following other modules and tools are needed as dependencies for our build environment:
- PowerShellGet and NuGet
PowerShellGet provides latest interfaces for PowerShellGallery.com where we are fetching our dependencies from and want to publish our stuff later together with NuGet. - PSDepend
PSDepend is really a nice module to define depending modules in onedependencies.psd1
file with versions and let them be processed all together. - PSake
We use PSake as our build task tool. - BuildHelpers
BuildHelpers is another module from really appreciated work of "RamblingCookieMonster". It helps to set up some consistent env variables for the build process. It e.g. provides an easy way to differentiate between AppVeyor and manual environment, provides additional Git information. - Pester
What else should we use as the well-known PowerShell testing framework Pester for our unit and integration testings? - PSScriptAnalyzer
Another module to provide information on PowerShell code quality and standard is PSScriptAnalyzer - platyPS
platyPS is used to generate external help files in MarkDown.
Both build processes use PSake for build automation.
PSake is using psakeFile
definitions for its tasks and workflows (dependencies, descriptions).
As both processes have different needs we extended this by the following logic:
build.ps1
is providing basic invocation wrapper.
It detects the build environment ($env:BHBuildEnvironment).- The
$psakeFile
is defined based on the schemapsakeFile.<Environment>.ps1
. Manual builds are usingUnknown
. Invoke-PSake
is called and the$psakeFile
is passed.
The following build tasks are defined for manual build and depend on each other:
Task | Description | Depends On (Unknown) | Depends On (AppVeyor) |
---|---|---|---|
Init | Initializing some stuff (here just some info output) | ||
Clean | Removes existing build outputs in build directory | init |
|
Compile (default) | Creates build output directory; 'compiles' one large psm1 -file by concatenating Private and Public ps1 into psm1 file |
Clean |
Clean |
Analyze | Invoking PSScriptAnalyzer , puts result output to build directory |
Compile |
|
PesterUnit | Runs Pester Tests with -Tag Unit |
Compile |
|
TestUnit | wrapper task | Analyze, PesterUnit |
Analyze, PesterUnit |
CreateMDHelp | generates module MD file by using New-MarkdownHelp |
Compile |
Compile |
UpdateMDHelp | updates generated module MD file by using Update-MarkdownHelpModule |
CreateMDHelp |
CreateMDHelp |
CreateExternalHelp | generates external help XML file from compiled psm1 -file by using New-ExternalHelp |
UpdateMDHelp |
UpdateMDHelp |
GenerateHelp | wrapper task | CreateExternalHelp |
CreateExternalHelp |
Build | adding additional content (GeneratedHelp header to psm1, LICENSE.md, README.md and RELEASENOTES.md files, ...) to build output | Compile, CreateExternalHelp |
Compile, CreateExternalHelp |
PesterIntegration | Runs Pester Tests with -Tag Integration |
Build |
|
TestIntegration | wrapper task | Analyze, PesterIntegration |
Analyze, PesterIntegration |
Publish | Publishs module from build output directory to PowerShellGallery by Publish-Module after complete Integration run |
TestIntegration |
So what I usually do is,
-
Develop my stuff, including tests
-
Execute
build.ps1 PesterUnit
, this will Compile psm1-file and test it -
Do manual testing by
- Import compiled module by using
Import-Module -Force <buildOutDir>\<modulename>\<modulename.psd1>
- do you testing...
- Import compiled module by using
-
Execute Integration Testing (will include all other important tasks)
build.ps1 TestIntegration
-
Commit / Push / PR
In future, releasing should be done automatically by AppVeyor CI/CD workflow. Until then, it is a manually process.
We use standard SemVer versioning, which means X.Y.Z
-
X --> MAJOR - a major change that makes APIs etc. incompatible or has really huge / important changes or added functionality (yeah very precise, I know 😉 ).
I would call it a upgrade.
-
Y --> MINOR - adding new functionality which is backward compatible.
I refer to this as update.
-
Z --> BUILD - the buildnumber, generated by AppVeyor CI/CD.
It might be that fixes are not released separately but included in next minor version update.
-
PowerShellGallery
We are releasing directly to powershellgallery.com by using our build task
build.ps1 Publish
.Publishing requires a version counter increase, otherwise publishing will fail. You need a API Key that needs to be set in your systems environment as
NUGET_API_KEY
(if you get this from me, I really really trust you 😊 ). -
Github
Additionally create a release on our Github page.
- Tag: vX.Y.Z (usually from master, as long as we don't have to many people working on master)
- Title: vX.Y.Z
- Text: I use the following template:
# Connect-MS365 vX.Y.Z :wave: Hi there, the first feature update release is out! Please share your Feedback, create Issues or raise your your Questions at our GitHub Issues. ## New Features - #<IssueId> description / title as in RELEASENOTES.md ## Changes - #<IssueId> description / title as in RELEASENOTES.md ## Fixes - #<IssueId> description / title as in RELEASENOTES.md ## Documentation * Install: [01-INSTALL.md](https://github.com/blindzero/Connect-MS365/blob/master/docs/01-INSTALLATION.md) * Usage: [02-USAGE.md](https://github.com/blindzero/Connect-MS365/blob/master/docs/02-USAGE.md)
- Binaries: I stopped manually attaching a nuget-package. If users want manual installations...they should have a look at powershellgallery.com.
The easiest way to contribute is:
- just create your fork in Github
- do your stuff
- create a pull request
The pull request shall contain
- "[#IssueId] Title"
- Detailed description what you did to achieve the title. What did you add? What did you change or delete? What is the improvement?
If you want to contribute directly, please contact us directly by creating an issue. But please understand that I will have some check here first...