Skip to content
Merged
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
29 changes: 18 additions & 11 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -717,21 +717,28 @@ described above:

| Need | Example |
|---|---|
| A complete advanced function | [Basic-Function-Example.ps1](../Documentation/Examples/Basic-Function-Example.ps1) |
| Module layout and export boundary | [Module-Structure-Example](../Documentation/Examples/Module-Structure-Example/) |
| Pester 6 tests, including private functions | [Module-Structure-Example/Tests](../Documentation/Examples/Module-Structure-Example/Tests/) |
| Mocking CIM and typed parameters | [Testing-Examples](../Documentation/Examples/Testing-Examples/) |
| A module template to copy | [Templates/Powershell-Module](../Templates/Powershell-Module/) |
| A complete advanced function | [Basic-Function-Example.ps1](../powershell-standards/Examples/Basic-Function-Example.ps1) |
| Module layout and export boundary | [Module-Structure-Example](../powershell-standards/Examples/Module-Structure-Example/) |
| Pester 6 tests, including private functions | [Module-Structure-Example/Tests](../powershell-standards/Examples/Module-Structure-Example/Tests/) |
| Mocking CIM and typed parameters | [Testing-Examples](../powershell-standards/Examples/Testing-Examples/) |
| A module template to copy | [Templates/Powershell-Module](https://github.com/fadwen/ai-powershell-standards/tree/main/Templates/Powershell-Module) |

[Test-QualityGates.ps1](../Documentation/Examples/Test-QualityGates.ps1) is the exception: it
intentionally violates these standards so the gates have something to catch. Never use it as a model.
[Test-QualityGates.ps1](https://github.com/fadwen/ai-powershell-standards/blob/main/Documentation/Anti-Patterns/Test-QualityGates.ps1)
is the exception: it intentionally violates these standards so the gates have something to catch.
Never use it as a model. It is not mirrored into consuming projects.

### Implementation Documentation

- [Implementation Guide](../Documentation/Implementation-Guide.md): Step-by-step setup and adoption
- [PowerShell Best Practices](../Documentation/PowerShell-Best-Practices.md): Comprehensive community standards
- [Enterprise Extensions](../Documentation/Enterprise-Extensions.md): Organizational customizations
- [Troubleshooting Guides](../Troubleshooting/): Organized problem-solving resources
These live in the standards repository only and are not mirrored into consuming projects:

- [Implementation Guide](https://github.com/fadwen/ai-powershell-standards/blob/main/Documentation/Implementation-Guide.md):
step-by-step setup and adoption
- [PowerShell Best Practices](https://github.com/fadwen/ai-powershell-standards/blob/main/Documentation/PowerShell-Best-Practices.md):
comprehensive community standards
- [Enterprise Extensions](https://github.com/fadwen/ai-powershell-standards/blob/main/Documentation/Enterprise-Extensions.md):
organizational customizations
- [Troubleshooting Guides](https://github.com/fadwen/ai-powershell-standards/tree/main/Troubleshooting):
organized problem-solving resources

---

Expand Down
2 changes: 1 addition & 1 deletion .github/instructions/comments.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ becomes a second copy that drifts.
## Help Generation Requirements

> **Worked example**:
> [Basic-Function-Example.ps1](../../Documentation/Examples/Basic-Function-Example.ps1) carries a
> [Basic-Function-Example.ps1](../../powershell-standards/Examples/Basic-Function-Example.ps1) carries a
> complete help block in the form described here - synopsis, description, per-parameter text,
> multiple examples with expected output, and `.NOTES` with troubleshooting links but no change
> history. It is a standalone function, so the whole block lives in the `.ps1`. In a module, that
Expand Down
4 changes: 2 additions & 2 deletions .github/instructions/errorsandlogs.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ Implement robust error handling and structured logging patterns for PowerShell c
correlation tracking, and enterprise-grade diagnostic capabilities.

> **Worked examples**:
> [Basic-Function-Example.ps1](../../Documentation/Examples/Basic-Function-Example.ps1) and
> [Get-ExampleData.ps1](../../Documentation/Examples/Module-Structure-Example/Public/Get-ExampleData.ps1)
> [Basic-Function-Example.ps1](../../powershell-standards/Examples/Basic-Function-Example.ps1) and
> [Get-ExampleData.ps1](../../powershell-standards/Examples/Module-Structure-Example/Public/Get-ExampleData.ps1)
> both generate a correlation ID once in `begin`, carry it through every message, use `$_` in the
> catch, and `continue` so one failed item does not abort the batch. Both failure paths are covered
> by tests.
Expand Down
6 changes: 3 additions & 3 deletions .github/instructions/module.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Collect essential information for module development:
## Module Structure Generation

> **Worked example**:
> [Documentation/Examples/Module-Structure-Example](../../Documentation/Examples/Module-Structure-Example/)
> [powershell-standards/Examples/Module-Structure-Example](../../powershell-standards/Examples/Module-Structure-Example/)
> is a small working module implementing everything in this section - the folder layout, the load
> order in `ModuleExample.psm1`, an explicit `FunctionsToExport`, a class used as a named output
> type, and a private helper that is never exported. Read it before generating a new module; prefer
Expand Down Expand Up @@ -94,7 +94,7 @@ comment block.
### Module Manifest Creation

Generate comprehensive module manifest (ModuleName.psd1). For a complete, valid manifest see
[ModuleExample.psd1](../../Documentation/Examples/Module-Structure-Example/ModuleExample.psd1) -
[ModuleExample.psd1](../../powershell-standards/Examples/Module-Structure-Example/ModuleExample.psd1) -
note that `FunctionsToExport` names each public function explicitly, which is what keeps private
helpers internal:

Expand Down Expand Up @@ -150,7 +150,7 @@ helpers internal:

Create optimized root module file (ModuleName.psm1). Load order matters: classes first, then private
functions, then public ones - see
[ModuleExample.psm1](../../Documentation/Examples/Module-Structure-Example/ModuleExample.psm1) for a
[ModuleExample.psm1](../../powershell-standards/Examples/Module-Structure-Example/ModuleExample.psm1) for a
working loader.

```powershell
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ InModuleScope MyModule {
```

Worked instance:
[Module-Structure-Example/Tests](../../../Documentation/Examples/Module-Structure-Example/Tests/)
[Module-Structure-Example/Tests](../../../powershell-standards/Examples/Module-Structure-Example/Tests/)
mocks a private function this way to exercise a per-item failure path.

## Global Mocks (Experimental)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ text descriptions and standard ASCII characters only.
## Standard Unit Test Structure

> Complete passing implementations of this template:
> [Module-Structure-Example/Tests](../../../Documentation/Examples/Module-Structure-Example/Tests/)
> [Module-Structure-Example/Tests](../../../powershell-standards/Examples/Module-Structure-Example/Tests/)
> and
> [Testing-Examples](../../../Documentation/Examples/Testing-Examples/).
> [Testing-Examples](../../../powershell-standards/Examples/Testing-Examples/).

Use this template for all unit tests:

Expand Down
9 changes: 5 additions & 4 deletions .github/instructions/pester.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,13 +214,14 @@ tests. Reach into the module instead.
These are complete, passing implementations of the patterns above. Prefer matching them over
inventing a structure:

- [Module-Structure-Example/Tests](../../Documentation/Examples/Module-Structure-Example/Tests/) -
- [Module-Structure-Example/Tests](../../powershell-standards/Examples/Module-Structure-Example/Tests/) -
module contract, class assertions, `InModuleScope` for two private functions, and a per-item
failure path
- [Testing-Examples/Basic-Function.Tests.ps1](../../Documentation/Examples/Testing-Examples/Basic-Function.Tests.ps1) -
- [Basic-Function.Tests.ps1](../../powershell-standards/Examples/Testing-Examples/Basic-Function.Tests.ps1) -
CIM mocking, `-RemoveParameterType`, `-TestCases`, and mock scoping across contexts
- [Tools/Tests](../../Tools/Tests/) - tests for the repository's own tooling, including fixture
files written per test and error-path coverage
- [Tools/Tests](https://github.com/fadwen/ai-powershell-standards/tree/main/Tools/Tests) - tests for the standards
repository's own tooling, including fixture files written per test and error-path coverage. Not
mirrored into consuming projects

### Documentation Integration

Expand Down
2 changes: 1 addition & 1 deletion .github/instructions/platyps.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ working the moment the repository moves or goes private. Reserve URLs for materi
lives outside the module, remembering that a user who installed from the Gallery has no repository
checkout — which is also why a relative path would be useless even if `Get-Help` accepted it.

[Module-Structure-Example](../../Documentation/Examples/Module-Structure-Example/docs/ModuleExample/Get-ExampleData.md)
[Module-Structure-Example](../../powershell-standards/Examples/Module-Structure-Example/docs/ModuleExample/Get-ExampleData.md)
shows both forms in one file.

Also fail the build on leftover placeholders — `Test-MarkdownCommandHelp` checks structure, not
Expand Down
2 changes: 1 addition & 1 deletion .github/instructions/style-enforcement.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description: 'Automatic style guide enforcement'
Automatically enforce PowerShell community style guidelines in all code generation.

> **Worked example**: every file under
> [Documentation/Examples](../../Documentation/Examples/) is written to these rules and passes the
> [powershell-standards/Examples](../../powershell-standards/Examples/) is written to these rules and passes the
> repository's own quality gates. When the wording here is ambiguous, match the examples.

## Mandatory Style Patterns
Expand Down
250 changes: 250 additions & 0 deletions powershell-standards/Examples/Basic-Function-Example.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
<#
.SYNOPSIS
Example of a basic enterprise-standard PowerShell function

.DESCRIPTION
This function demonstrates all the key patterns and standards
required for enterprise PowerShell development using our
GitHub Copilot standards.

.PARAMETER ComputerName
[String[]] (Mandatory: Yes, Pipeline: ByValue, ByPropertyName)

One or more computer names to query for information.

VALIDATION:
- Must be valid computer names (NetBIOS or FQDN)
- Pattern: Letters, numbers, hyphens, and dots only
- Length: 1-255 characters

EXAMPLES:
- "SERVER01"
- "web01.contoso.com"
- @("APP01", "APP02", "DB01")

.PARAMETER Credential
[PSCredential] (Mandatory: No, Pipeline: No)

Credential to use for remote connections. If not provided,
uses current user context.

.PARAMETER IncludeServices
[Switch] (Mandatory: No, Pipeline: No)

Include running services information in the output.

.EXAMPLE
PS> Get-BasicServerInfo -ComputerName "SERVER01"

DESCRIPTION: Basic server information retrieval
OUTPUT: Server information object with OS and hardware details
USE CASE: Daily server health checks

.EXAMPLE
PS> @("WEB01", "WEB02") | Get-BasicServerInfo -IncludeServices

DESCRIPTION: Pipeline processing with service information
OUTPUT: Server info objects including running services
USE CASE: Comprehensive server inventory

.EXAMPLE
PS> Get-BasicServerInfo -ComputerName "PROD-DB01" -Credential $cred | Export-Csv "server-info.csv"

DESCRIPTION: Secure connection with credential and export
OUTPUT: CSV file with server information
USE CASE: Automated reporting for compliance

.NOTES
Author: Jeffrey Stuhr
Blog: https://www.techbyjeff.net
LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/
Version: 1.0.0
Last Updated: 2024-01-15

DEPENDENCIES:
- PowerShell 7.6 (LTS) or Windows PowerShell 5.1
- WinRM enabled on target computers
- Appropriate permissions on target systems

PERFORMANCE:
- Execution time: ~10-30 seconds per server
- Memory usage: ~5MB per server
- Network bandwidth: Minimal (WMI queries only)

TROUBLESHOOTING:
- Connection issues: .\Troubleshooting\Common\Function-Issues.md
- WinRM problems: .\Troubleshooting\Security\WinRM-Setup.md
- Performance: .\Troubleshooting\Performance\Server-Monitoring.md
#>

function Get-BasicServerInfo {
[CmdletBinding(SupportsShouldProcess = $true)]
# Quoted, and named. [OutputType([PSCustomObject])] is the anti-pattern the
# standards call out: it tells a caller nothing about the shape returned.
[OutputType('BasicServerInfo')]
param(
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage = "Enter one or more computer names")]
[ValidateNotNullOrEmpty()]
[ValidatePattern('^[a-zA-Z0-9\-\.]+$')]
[ValidateLength(1, 255)]
[Alias('CN', 'ServerName', 'Name')]
[string[]]$ComputerName,

[Parameter()]
[System.Management.Automation.PSCredential]
[System.Management.Automation.Credential()]
$Credential,

[Parameter()]
[switch]$IncludeServices
)

begin {
# Initialize correlation tracking for enterprise monitoring
$correlationId = [System.Guid]::NewGuid()
$startTime = Get-Date

Write-Verbose "Starting $($MyInvocation.MyCommand.Name) - CorrelationId: $correlationId"
Write-Verbose "Parameters: ComputerName count = $($ComputerName.Count), IncludeServices = $IncludeServices"

# Initialize counters for performance tracking
$processedCount = 0
$successCount = 0
$failureCount = 0

# Prepare credential for CIM sessions if provided
$cimSessionOptions = New-CimSessionOption -Protocol WSMan
$sessionParams = @{
SessionOption = $cimSessionOptions
ErrorAction = 'Stop'
}
if ($Credential) {
$sessionParams.Credential = $Credential
}
}

process {
foreach ($computer in $ComputerName) {
$processedCount++
$computerStartTime = Get-Date

try {
Write-Verbose "Processing computer: $computer (CorrelationId: $correlationId)"

if ($PSCmdlet.ShouldProcess($computer, "Retrieve server information")) {

# Test connectivity first
$connectionTest = Test-Connection -ComputerName $computer -Count 1 -Quiet -ErrorAction SilentlyContinue
if (-not $connectionTest) {
throw "Computer $computer is not reachable via ping"
}

# Create CIM session for efficient querying
$sessionParams.ComputerName = $computer
$cimSession = New-CimSession @sessionParams

try {
# Gather basic system information
Write-Verbose "Querying system information for $computer"
$os = Get-CimInstance -CimSession $cimSession -ClassName Win32_OperatingSystem
$computerSystem = Get-CimInstance -CimSession $cimSession -ClassName Win32_ComputerSystem
$processor = Get-CimInstance -CimSession $cimSession -ClassName Win32_Processor | Select-Object -First 1

# Calculate uptime
$uptime = (Get-Date) - $os.LastBootUpTime

# Build result object with raw data for maximum reusability.
# PSTypeName gives the output a name that matches [OutputType].
$result = [PSCustomObject]@{
PSTypeName = 'BasicServerInfo'
ComputerName = $computer
OperatingSystem = $os.Caption
Version = $os.Version
ServicePackLevel = $os.ServicePackMajorVersion
Architecture = $os.OSArchitecture
LastBootTime = $os.LastBootUpTime
UptimeDays = [math]::Round($uptime.TotalDays, 2)
TotalMemoryGB = [math]::Round($computerSystem.TotalPhysicalMemory / 1GB, 2)
Manufacturer = $computerSystem.Manufacturer
Model = $computerSystem.Model
ProcessorName = $processor.Name
ProcessorCores = $processor.NumberOfCores
ProcessorLogicalProcessors = $processor.NumberOfLogicalProcessors
Domain = $computerSystem.Domain
Workgroup = $computerSystem.Workgroup
CorrelationId = $correlationId
QueryTime = Get-Date
QueryDurationMs = ((Get-Date) - $computerStartTime).TotalMilliseconds
}

# Add services information if requested
if ($IncludeServices) {
Write-Verbose "Querying services information for $computer"
$runningServices = Get-CimInstance -CimSession $cimSession -ClassName Win32_Service |
Where-Object State -eq 'Running' |
Select-Object Name, DisplayName, StartMode

$result | Add-Member -MemberType NoteProperty -Name 'RunningServices' -Value $runningServices
$result | Add-Member -MemberType NoteProperty -Name 'RunningServiceCount' -Value $runningServices.Count
}

$successCount++
Write-Verbose "Successfully processed $computer in $([math]::Round($result.QueryDurationMs, 0))ms"

# Return the result object
$result

}
finally {
# Clean up CIM session
if ($cimSession) {
Remove-CimSession -CimSession $cimSession -ErrorAction SilentlyContinue
}
}
}

}
catch {
$failureCount++
$errorDetails = @{
ComputerName = $computer
CorrelationId = $correlationId
ErrorMessage = $_.Exception.Message
ErrorTime = Get-Date
Function = $MyInvocation.MyCommand.Name
}

Write-Error "Failed to process computer '$computer': $($_.Exception.Message) (CorrelationId: $correlationId)"
Write-Verbose "Error details: $($errorDetails | ConvertTo-Json -Compress)"

# Continue processing other computers instead of terminating
continue
}
}
}

end {
$endTime = Get-Date
$totalDuration = ($endTime - $startTime).TotalSeconds

# Performance and summary logging
$summary = @{
CorrelationId = $correlationId
TotalComputers = $processedCount
SuccessfulQueries = $successCount
FailedQueries = $failureCount
SuccessRate = if ($processedCount -gt 0) { [math]::Round(($successCount / $processedCount) * 100, 1) } else { 0 }
TotalDurationSeconds = [math]::Round($totalDuration, 2)
AverageTimePerServer = if ($successCount -gt 0) { [math]::Round($totalDuration / $successCount, 2) } else { 0 }
}

Write-Verbose "Completed $($MyInvocation.MyCommand.Name) - Summary: $($summary | ConvertTo-Json -Compress)"

if ($failureCount -gt 0) {
Write-Warning "Operation completed with $failureCount failures out of $processedCount computers. Check error messages above for details."
}
}
}
Loading