Skip to content

Commit 7cafa07

Browse files
authored
Merge pull request #1 from keithbabinec/dev/keith/reorganizationUpdates
Module example updates, structure reorganization
2 parents 553333d + cae5a39 commit 7cafa07

25 files changed

+319
-44
lines changed

README.md

+35-10
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,41 @@
11
# PowerShellModuleStarterKit
2-
A starter kit for new PowerShell script modules including unit tests.
2+
A starter kit for new PowerShell script modules.
33

4-
Contains a basic .psd1 (manifest) and .psm1 (root module), class examples, function examples, and unit tests.
4+
This repository demonstrates how to organize a module and accomplish a few common tasks like public vs private functions, unit tests, working with classes, and more.
55

6-
### Prerequisites
6+
## Features
7+
* Public and private functions.
8+
* Unit testing with the Pester framework.
9+
* PowerShell v5 classes (accessible inside and outside the module scope).
10+
* Module private data (for constants and caching).
11+
* Referencing static resource files.
12+
* Referencing external libraries.
713

8-
In order to run the unit tests, you need the Pester test framework (https://github.com/pester/Pester).
14+
## Setup
15+
1. Clone the repository to your local computer.
16+
2. Open PowerShell (as an administrator).
17+
3. Install the [Pester](https://github.com/pester/Pester) framework:
18+
``` powershell
19+
Install-Module -Name Pester -MinimumVersion 4.6.0 -Scope AllUsers -Force -SkipPublisherCheck
20+
```
21+
4. Add this starter kit module to the PSModule path.
22+
***Note: This assumes you have cloned the repository to C:\Source. Update the path if this isn't correct.***
23+
``` powershell
24+
$repoDirectory = 'C:\Source\PowerShellModuleStarterKit'
25+
$existingPaths = $ENV:PSModulePath
26+
$newPaths = "$repoDirectory;$existingPaths"
27+
$scope = [System.EnvironmentVariableTarget]::Machine
28+
[System.Environment]::SetEnvironmentVariable('PSModulePath',$newPaths,$scope)
29+
```
930

10-
Once Pester is installed and in your PSModule path, run the Invoke-Pester cmdlet (no args) from the root of the repository. It will find and run the tests for you.
31+
## Load the module
32+
``` powershell
33+
Import-Module -Name SampleModule
34+
```
1135

12-
### Notes
13-
14-
This module can be loaded by calling Import-Module on either the module manifest (.psd1), or the root module (.psm1) file.
15-
16-
For the sake of easy unit testing, the .psm1 file is referenced for test imports. This is because the private functions and (non-exportable classes) are easily found and loaded.
36+
## Run the unit tests
37+
***Note: This assumes you have cloned the repository to C:\Source. Update the path if this isn't correct.***
38+
``` powershell
39+
cd 'C:\Source\PowerShellModuleStarterKit\SampleModule'
40+
Invoke-Pester
41+
```
File renamed without changes.
File renamed without changes.

Functions/Private/Get-RandomItemsCount.ps1 renamed to SampleModule/Functions/Private/Get-RandomItemsCount.ps1

+5-5
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ function Get-RandomItemsCount
55
Gets a random item count.
66
77
.DESCRIPTION
8-
Gets a random item count.
8+
Gets a random item count, between the numbers 1 and 8.
99
1010
.EXAMPLE
11-
Get-RandomItemsCount
11+
PS C:\> Get-RandomItemsCount
12+
Returns a random items count.
1213
#>
13-
1414
[CmdletBinding()]
1515
Param
16-
()
17-
16+
(
17+
)
1818
Process
1919
{
2020
# generate a random number and return it
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
function Get-ModuleCacheItems
2+
{
3+
<#
4+
.SYNOPSIS
5+
Returns some items from the writable module cache.
6+
7+
.DESCRIPTION
8+
Returns some items from the writable module cache. If no items are in the cache, then nothing will be returned.
9+
10+
.EXAMPLE
11+
PS C:\> Get-ModuleCacheItems
12+
Returns the items saved in the cache, if they are found.
13+
#>
14+
[CmdletBinding()]
15+
Param
16+
(
17+
)
18+
Process
19+
{
20+
# example: read the first cache object, return it to the pipeline.
21+
Write-Output $MyInvocation.MyCommand.Module.PrivateData.Cache['Example1Key']
22+
23+
# example: read the second cache object, return it to the pipeline.
24+
Write-Output $MyInvocation.MyCommand.Module.PrivateData.Cache['Example2Key']
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
function Get-ModulePrivateDataConstant
2+
{
3+
<#
4+
.SYNOPSIS
5+
Returns a constant setting from the module private data.
6+
7+
.DESCRIPTION
8+
Returns a constant setting from the module private data. If the setting is not found, null will be returned.
9+
10+
.PARAMETER SettingName
11+
The name of the constant setting to return.
12+
13+
.EXAMPLE
14+
PS C:\> Get-ModulePrivateDataSetting -SettingName 'example'
15+
Returns the module private data constant with key 'example'
16+
#>
17+
[CmdletBinding()]
18+
Param
19+
(
20+
[Parameter(Mandatory=$true)]
21+
[System.String]
22+
$SettingName
23+
)
24+
Process
25+
{
26+
# capture the constant to a variable for use
27+
$constant = $MyInvocation.MyCommand.Module.PrivateData.Constants[$SettingName]
28+
29+
# write it to the output pipeline.
30+
Write-Output $constant
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
function Get-ResourceFileContents
2+
{
3+
<#
4+
.SYNOPSIS
5+
Returns the contents of a sample resource file packaged with the module.
6+
7+
.DESCRIPTION
8+
Returns the contents of a sample resource file packaged with the module.
9+
10+
.EXAMPLE
11+
PS C:\> Get-ResourceFileContents
12+
Returns the contents of the sample resource file.
13+
#>
14+
[CmdletBinding()]
15+
Param
16+
(
17+
)
18+
Process
19+
{
20+
$moduleRoot = $MyInvocation.MyCommand.Module.ModuleBase
21+
$filePath = Join-Path -Path $moduleRoot -ChildPath "Resources\Templates\TemplateResource.json"
22+
$fileContents = Get-Content -Path $filePath -Raw
23+
24+
Write-Output $fileContents
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
function Invoke-ReferencedLibrary
2+
{
3+
<#
4+
.SYNOPSIS
5+
Invokes the external library referenced by the module manifest.
6+
7+
.DESCRIPTION
8+
Invokes the external library referenced by the module manifest. The referenced library is Newtonsoft.JSON.
9+
The test will serialize an object to JSON and return the contents.
10+
11+
.EXAMPLE
12+
PS C:\> Invoke-ReferencedLibrary
13+
Tests the referenced library by serializing an object and returning the contents.
14+
#>
15+
[CmdletBinding()]
16+
Param
17+
(
18+
)
19+
Process
20+
{
21+
# the purpose of this function is to use the library that was referenced in the module manifest.
22+
# the library is Newtonsoft.JSON, which is used for serializing and deserializing JSON data.
23+
# in this example we just make a new class object and serialize it.
24+
25+
# NOTE: the library doesn't need to be loaded here with Add-Type, since it is already loaded automatically when the module was imported.
26+
27+
$item = New-AdvancedItem -Name 'Test'
28+
29+
$itemAsJson = [Newtonsoft.Json.JsonConvert]::SerializeObject($item)
30+
31+
Write-Output $itemAsJson
32+
}
33+
}

Functions/Public/New-AdvancedItem.ps1 renamed to SampleModule/Functions/Public/New-AdvancedItem.ps1

+2-3
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,16 @@ function New-AdvancedItem
1111
The name of the item.
1212
1313
.EXAMPLE
14-
New-AdvancedItem -Name 'Test'
14+
PS C:\> New-AdvancedItem -Name 'Test'
15+
Returns a new advanced item object.
1516
#>
16-
1717
[CmdletBinding()]
1818
Param
1919
(
2020
[Parameter(Mandatory=$true)]
2121
[System.String]
2222
$Name
2323
)
24-
2524
Process
2625
{
2726
# make the new item and return it

Functions/Public/New-SimpleItem.ps1 renamed to SampleModule/Functions/Public/New-SimpleItem.ps1

+2-3
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ function New-SimpleItem
1414
The age of the item.
1515
1616
.EXAMPLE
17-
New-SimpleItem -Name 'Test' -Age 42
17+
PS C:\> New-SimpleItem -Name 'Test' -Age 42
18+
Returns a new simple item object.
1819
#>
19-
2020
[CmdletBinding()]
2121
Param
2222
(
@@ -28,7 +28,6 @@ function New-SimpleItem
2828
[System.Int32]
2929
$Age
3030
)
31-
3231
Process
3332
{
3433
# make the new item and return it
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
function Set-ModuleCacheItems
2+
{
3+
<#
4+
.SYNOPSIS
5+
Saves some items to the writable module cache.
6+
7+
.DESCRIPTION
8+
Saves some items to the writable module cache.
9+
10+
.EXAMPLE
11+
PS C:\> Set-ModuleCacheItems
12+
Saves a simple type and a complex type to the cache.
13+
#>
14+
[CmdletBinding()]
15+
Param
16+
(
17+
)
18+
Process
19+
{
20+
# example: write to the cache, save a primitive type like string, or int.
21+
$MyInvocation.MyCommand.Module.PrivateData.Cache['Example1Key'] = 'example1Value'
22+
23+
# example: write to the cache, save a complex type like a class object.
24+
$advancedType = New-AdvancedItem -Name 'Test'
25+
$MyInvocation.MyCommand.Module.PrivateData.Cache['Example2Key'] = $advancedType
26+
}
27+
}
659 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"ID": 1,
3+
"Name": "Sample document",
4+
"Description": "This is a JSON blob that can be loaded as a module resource. See the module manifest to see how the file is referenced for packaging."
5+
}
Binary file not shown.
File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# load the module
2+
Import-Module SampleModule -Force
3+
4+
# run tests
5+
# note: the Get-RandomItemsCount function is private (non-exported).
6+
# in order for Pester to test this function, use the 'InModuleScope' wrapper around the test.
7+
8+
InModuleScope SampleModule {
9+
Describe "Get-RandomItemsCount private function tests" {
10+
Context "Basic functionality" {
11+
It "Function can be loaded." {
12+
{
13+
Get-Help Get-RandomItemsCount -ErrorAction Stop
14+
} | Should Not Throw
15+
}
16+
It "Returns a single integer output" {
17+
$count = Get-RandomItemsCount
18+
$count | Should Not Be $null
19+
$count.GetType().FullName | Should Be 'System.Int32'
20+
}
21+
}
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# load the module
2+
Import-Module SampleModule -Force
3+
4+
# run tests
5+
Describe "Get-ModulePrivateDataConstant public function tests" {
6+
Context "Basic functionality" {
7+
It "Function can be loaded." {
8+
{
9+
Get-Help Get-ModulePrivateDataConstant -ErrorAction Stop
10+
} | Should Not Throw
11+
}
12+
It "Returns null for a non-existent module constant" {
13+
$item = Get-ModulePrivateDataConstant -SettingName 'Not a real constant'
14+
$item | Should Be $null
15+
}
16+
It "Returns the correct value for the string sample constant" {
17+
$item = Get-ModulePrivateDataConstant -SettingName 'ExampleStringConst'
18+
$item | Should Not Be $null
19+
$item.GetType().FullName | Should Be 'System.String'
20+
}
21+
It "Returns the correct value for the int sample constant" {
22+
$item = Get-ModulePrivateDataConstant -SettingName 'ExampleIntConst'
23+
$item | Should Not Be $null
24+
$item.GetType().FullName | Should Be 'System.Int32'
25+
}
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# load the module
2+
Import-Module SampleModule -Force
3+
4+
# run tests
5+
Describe "Get-ResourceFileContents public function tests" {
6+
Context "Basic functionality" {
7+
It "Function can be loaded." {
8+
{
9+
Get-Help Get-ResourceFileContents -ErrorAction Stop
10+
} | Should Not Throw
11+
}
12+
It "Returns a json document" {
13+
$item = Get-ResourceFileContents
14+
$item | Should Not Be $null
15+
$item.GetType().FullName | Should Be 'System.String'
16+
$item.StartsWith("{") | Should Be $true
17+
$item.EndsWith("}") | Should Be $true
18+
}
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# load the module
2+
Import-Module SampleModule -Force
3+
4+
# run tests
5+
Describe "Invoke-ReferencedLibrary public function tests" {
6+
Context "Basic functionality" {
7+
It "Function can be loaded." {
8+
{
9+
Get-Help Invoke-ReferencedLibrary -ErrorAction Stop
10+
} | Should Not Throw
11+
}
12+
It "Returns a json document" {
13+
$item = Invoke-ReferencedLibrary
14+
$item | Should Not Be $null
15+
$item.GetType().FullName | Should Be 'System.String'
16+
$item.StartsWith("{") | Should Be $true
17+
$item.EndsWith("}") | Should Be $true
18+
}
19+
}
20+
}

Tests/New-AdvancedItem.tests.ps1 renamed to SampleModule/Tests/Functions/Public/New-AdvancedItem.tests.ps1

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# load the module
2-
Import-Module .\ScriptModule.psm1
2+
Import-Module SampleModule -Force
33

44
# run tests
55
Describe "New-AdvancedItem public function tests" {
@@ -11,7 +11,7 @@ Describe "New-AdvancedItem public function tests" {
1111
}
1212
It "Returns the correct type" {
1313
$item = New-AdvancedItem -Name 'Cool'
14-
$item -eq $null | Should Be $false
14+
$item | Should Not Be $null
1515
$item.GetType().FullName | Should Be 'AdvancedClass'
1616
}
1717
It "Name property is set" {
@@ -24,7 +24,7 @@ Describe "New-AdvancedItem public function tests" {
2424
}
2525
It "Items collection is populated" {
2626
$item = New-AdvancedItem -Name 'Cool'
27-
$item.Items -ne $null | Should Be $true
27+
$item.Items | Should Not Be $null
2828
$item.Items.Count | Should BeGreaterThan 0
2929
}
3030
}

0 commit comments

Comments
 (0)