Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Needed for publishing of examples, build worker defaults to core.autocrlf=input.
* text eol=autocrlf

# Preserve the published guidance scripts' upstream LF representation.
guidance/* text eol=lf

*.mof text eol=crlf
*.sh text eol=lf
*.svg eol=lf
Expand Down
1 change: 1 addition & 0 deletions docs/content/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Look up the framework's exact contracts and the module-development standards it
| [Scenario matrix](reference/scenario-matrix.md) | Which jobs run for each trigger scenario. |
| [Framework test IDs](reference/framework-test-ids.md) | The framework tests enforced on source code and on the built module. |
| [Dependencies](reference/dependencies.md) | The actions, modules, and services the workflow composes. |
| [PowerShell guidance scripts](reference/guidance-scripts.md) | Runnable reference scripts for common PowerShell implementation patterns. |

## Specification

Expand Down
31 changes: 31 additions & 0 deletions docs/content/reference/guidance-scripts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
title: PowerShell guidance scripts
description: Reference PowerShell scripts that demonstrate implementation patterns used by the PSModule framework.
---

# PowerShell guidance scripts

The [guidance directory](https://github.com/PSModule/Process-PSModule/tree/main/guidance) contains runnable reference scripts for common PowerShell implementation patterns. They are framework learning assets, not Process-PSModule module source, so they are intentionally kept outside `src/` and are not included in a module manifest.

Run an individual script only when its scenario is suitable for the local environment. Several scripts create temporary files, make web requests, or measure execution time.

| Script | Focus |
| --- | --- |
| `Add-Array.ps1` | Array and generic list population |
| `Add-HashTable.ps1` | Hashtable population styles |
| `Add-String.ps1` | String construction approaches |
| `Caller.ps1` | Caller discovery through the PowerShell call stack |
| `ClassExtension.ps1` | Class inheritance and base-method invocation |
| `Loops.ps1` | Loop and function-call overhead |
| `Out-Null.ps1` | Discarding command output |
| `PipelineExecution.ps1` | Pipeline parameter evaluation and lifecycle blocks |
| `PSCallStack.ps1` | Nested-call stack inspection |
| `PSCmdlet.ps1` | The `$PSCmdlet` variable at nested call levels |
| `PSModuleTest.psm1` | Module-component import and exports |
| `Read-File.ps1` | File-reading approaches |
| `root.ps1` | Nested PSScriptAnalyzer binary-module loading |
| `WebCalls.ps1` | Web-request protocol comparisons |

## Maintenance

The files were imported byte-for-byte from [`PSModule/docs/guidance`](https://github.com/PSModule/docs/tree/main/guidance). When that published set changes, import the complete current file from its Git blob into this directory and preserve its contents. Keep this index synchronized with the directory so users can discover every available script.
1 change: 1 addition & 0 deletions docs/zensical.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ nav = [
{"Scenario matrix" = "reference/scenario-matrix.md"},
{"Framework test IDs" = "reference/framework-test-ids.md"},
{"Dependencies" = "reference/dependencies.md"},
{"PowerShell guidance scripts" = "reference/guidance-scripts.md"},
]},
{"Specification" = [
"specification/index.md",
Expand Down
50 changes: 50 additions & 0 deletions guidance/Add-Array.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
$tests = @{
'PowerShell Explicit Assignment' = {
param($count)

$result = foreach ($i in 1..$count) {
$i
}
$null = $result # just added for linter issues
}
'.Add(..) to List<T>' = {
param($count)

$result = [Collections.Generic.List[int]]::new()
foreach ($i in 1..$count) {
$result.Add($i)
}
}
'+= Operator to Array' = {
param($count)

$result = @()
foreach ($i in 1..$count) {
$result += $i
}
}
}

5kb, 10kb, 100kb | ForEach-Object {
$groupResult = foreach ($test in $tests.GetEnumerator()) {
$ms = (Measure-Command { & $test.Value -Count $_ }).TotalMilliseconds

[pscustomobject]@{
CollectionSize = $_
Test = $test.Key
TotalMilliseconds = [math]::Round($ms, 2)
}

[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}

$groupResult = $groupResult | Sort-Object TotalMilliseconds
$groupResult | Select-Object *, @{
Name = 'RelativeSpeed'
Expression = {
$relativeSpeed = $_.TotalMilliseconds / $groupResult[0].TotalMilliseconds
[math]::Round($relativeSpeed, 2).ToString() + 'x'
}
}
}
17 changes: 17 additions & 0 deletions guidance/Add-HashTable.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Adding 10000 items property-style
Measure-Command {
$HashProp = @{}
1..10000 | ForEach-Object { $HashProp.$_ = $_ }
}

# Adding 10000 items using the Add method
Measure-Command {
$HashMethod = @{}
1..10000 | ForEach-Object { $HashMethod.Add($_, $_) }
}

# Adding 10000 items dictionary-style
Measure-Command {
$HashDict = @{}
1..10000 | ForEach-Object { $HashDict[$_] = $_ }
}
48 changes: 48 additions & 0 deletions guidance/Add-String.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
$tests = @{
'StringBuilder' = {
$sb = [System.Text.StringBuilder]::new()
foreach ($i in 0..$args[0]) {
$sb = $sb.AppendLine("Iteration $i")
}
$sb.ToString()
}
'Join operator' = {
$string = @(
foreach ($i in 0..$args[0]) {
"Iteration $i"
}
) -join "`n"
$string
}
'Addition Assignment +=' = {
$string = ''
foreach ($i in 0..$args[0]) {
$string += "Iteration $i`n"
}
$string
}
}

10kb, 50kb, 100kb | ForEach-Object {
$groupResult = foreach ($test in $tests.GetEnumerator()) {
$ms = (Measure-Command { & $test.Value $_ }).TotalMilliseconds

[pscustomobject]@{
Iterations = $_
Test = $test.Key
TotalMilliseconds = [math]::Round($ms, 2)
}

[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}

$groupResult = $groupResult | Sort-Object TotalMilliseconds
$groupResult | Select-Object *, @{
Name = 'RelativeSpeed'
Expression = {
$relativeSpeed = $_.TotalMilliseconds / $groupResult[0].TotalMilliseconds
[math]::Round($relativeSpeed, 2).ToString() + 'x'
}
}
}
54 changes: 54 additions & 0 deletions guidance/Caller.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
function Invoke-Function4 {
<#
.SYNOPSIS
Demonstrates caller detection using Get-PSCallStack.
#>
[CmdletBinding()]
param()
'In: ' + $MyInvocation.InvocationName
$caller = (Get-PSCallStack)[1].Command
'Caller: ' + $caller
}

function Invoke-Function3 {
<#
.SYNOPSIS
Demonstrates nested caller detection at depth 3.
#>
[CmdletBinding()]
param()
'In: ' + $MyInvocation.InvocationName
$caller = (Get-PSCallStack)[1].Command
'Caller: ' + $caller
Invoke-Function4
}

function Invoke-Function2 {
<#
.SYNOPSIS
Demonstrates nested caller detection at depth 2.
#>
[CmdletBinding()]
param()
'In: ' + $MyInvocation.InvocationName
$caller = (Get-PSCallStack)[1].Command
'Caller: ' + $caller
Invoke-Function3
}

function Invoke-Function1 {
<#
.SYNOPSIS
Entry point demonstrating caller detection through the call stack.
#>
[CmdletBinding()]
param()
'In: ' + $MyInvocation.InvocationName
Get-PSCallStack
$caller = (Get-PSCallStack)[1].Command
'Caller: ' + $caller
Invoke-Function2
}

# Test the functions
Invoke-Function1
41 changes: 41 additions & 0 deletions guidance/ClassExtension.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
class C {
[string]$Name

C([string]$Name) {
$this.Name = $Name
}

[string]GetInfo() {
return "Name: $($this.Name)"
}
}

class B : C {
[int]$Age

B([string]$Name, [int]$Age) : base($Name) {
$this.Age = $Age
}

[string]GetInfo() {
# Cast $this to the parent class (C) to call its GetInfo()
return "$(([C]$this).GetInfo()), Age: $($this.Age)"
}
}

class A : B {
[string]$Role

A([string]$Name, [int]$Age, [string]$Role) : base($Name, $Age) {
$this.Role = $Role
}

[string]GetInfo() {
# Cast $this to B to call B’s GetInfo(), which itself calls C’s GetInfo()
return "$(([B]$this).GetInfo()), Role: $($this.Role)"
}
}

# Creating and testing an instance
$person = [A]::new('John Doe', 30, 'Manager')
$person.GetInfo()
38 changes: 38 additions & 0 deletions guidance/Loops.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
$ranGen = New-Object System.Random
$RepeatCount = 10000

'Basic for-loop = {0}ms' -f (Measure-Command -Expression {
for ($i = 0; $i -lt $RepeatCount; $i++) {
$Null = $ranGen.Next()
}
}).TotalMilliseconds

'Wrapped in a function = {0}ms' -f (Measure-Command -Expression {
function Get-RandNum_Core {
<#
.SYNOPSIS
Gets a random number using a shared Random instance.
#>
param ($ranGen)
$ranGen.Next()
}

for ($i = 0; $i -lt $RepeatCount; $i++) {
$Null = Get-RandNum_Core $ranGen
}
}).TotalMilliseconds

'For-loop in a function = {0}ms' -f (Measure-Command -Expression {
function Get-RandNum_All {
<#
.SYNOPSIS
Gets random numbers in a loop using a shared Random instance.
#>
param ($ranGen)
for ($i = 0; $i -lt $RepeatCount; $i++) {
$Null = $ranGen.Next()
}
}

Get-RandNum_All $ranGen
}).TotalMilliseconds
50 changes: 50 additions & 0 deletions guidance/Out-Null.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
$tests = @{
'Assign to $null' = {
$arrayList = [System.Collections.ArrayList]::new()
foreach ($i in 0..$args[0]) {
$null = $arraylist.Add($i)
}
}
'Cast to [void]' = {
$arrayList = [System.Collections.ArrayList]::new()
foreach ($i in 0..$args[0]) {
[void]$arraylist.Add($i)
}
}
'Redirect to $null' = {
$arrayList = [System.Collections.ArrayList]::new()
foreach ($i in 0..$args[0]) {
$arraylist.Add($i) > $null
}
}
'Pipe to Out-Null' = {
$arrayList = [System.Collections.ArrayList]::new()
foreach ($i in 0..$args[0]) {
$arraylist.Add($i) | Out-Null
}
}
}

10kb, 50kb, 100kb | ForEach-Object {
$groupResult = foreach ($test in $tests.GetEnumerator()) {
$ms = (Measure-Command { & $test.Value $_ }).TotalMilliseconds

[pscustomobject]@{
Iterations = $_
Test = $test.Key
TotalMilliseconds = [math]::Round($ms, 2)
}

[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}

$groupResult = $groupResult | Sort-Object TotalMilliseconds
$groupResult | Select-Object *, @{
Name = 'RelativeSpeed'
Expression = {
$relativeSpeed = $_.TotalMilliseconds / $groupResult[0].TotalMilliseconds
[math]::Round($relativeSpeed, 2).ToString() + 'x'
}
}
}
Loading