PowerShell / Basic: editions, objects, types, and functions / Lesson 5
Design functions with precise output contracts
Declare parameters, validate inputs, manage scope, and keep incidental success output from corrupting a reusable function’s result.
On this page
Lesson goals
- Use parameter metadata and validation as a public interface.
- Explain success-stream output versus return control flow.
- Separate local state, script scope, and caller expectations.
A function’s parameters are a schema
A function can declare typed parameters, mandatory input, validation attributes, parameter sets, and defaults. An advanced function with CmdletBinding gains common-parameter behavior and a richer binding contract. Type conversion is not application validation: an accepted integer can still be outside the supported domain, and a nonempty path can still be unauthorized. Keep defaults deliberate and reject unsupported combinations before effects. Parameter names and output shape become a public interface that callers and tests rely on. Avoid allowing positional convenience or a broad remaining-arguments parameter to conceal malformed input silently.
Every uncaptured success result can become function output
PowerShell functions can emit objects from any statement that writes to the success stream. return can exit a function and emit an expression, but it does not erase objects emitted earlier. A helper message, method return value, or accidental expression can therefore change the result from one object into several. Use appropriate diagnostic streams and intentionally suppress irrelevant method results where needed. Do not call every unexpected object a pipeline bug before inspecting the function body. A clean output contract names emitted types, properties, cardinality, and ordering as well as the normal successful values.
Scope controls state visibility, not transactional isolation
Local, script, global, module, and invocation scopes influence variable resolution and mutation. Dot-sourcing runs content in the current scope and is trusted code execution, not data loading. A child scope or module does not roll back file, service, or external effects if a function fails. Prefer explicit parameters and returned data over hidden global coupling. A function that changes preference variables, current location, or shared objects should restore or document that behavior. Tests must cover repeated calls and alternate callers, because a single console run can accidentally inherit the state that makes an incomplete function seem correct.
Keep the function’s synthetic result explicit
The illustration validates a small mode vocabulary and emits one constructed record. It does not enumerate a host provider or change state. The name, count, and enabled fields are explicit data rather than a formatted table. It remains non-runnable because PowerShell is absent. In a later engine test, assert the actual output type and cardinality, including invalid modes, rather than comparing only the console view. If a method or helper is added later, the regression should catch incidental success output before it reaches a serializer or caller.
function New-LabRecord {
[CmdletBinding()]
param(
[ValidateSet('Review', 'Archive')]
[string]$Mode = 'Review'
)
[pscustomobject]@{ Mode = $Mode; Enabled = $true; Count = 2 }
}
New-LabRecord -Mode Review
The intended result is one object with the declared properties. No runtime assertion was performed, so this block makes no claim about engine binding or display formatting.
Verify input and result intent offline, then distinguish engine tests
A Python fixture can assert a declared mode vocabulary and record schema. It must not be called a test of ValidateSet, PowerShell scope, or the common-parameter binder. Keep malformed input rejection and a stable output schema visible in the model. Later authorized PowerShell tests should also inspect side effects, preference restoration, nested calls, and error behavior. A clear function boundary makes each layer reviewable without executing a real administrative action. This course validates only the harmless model and leaves host-changing functions, credentials, and remote operations illustrative and non-runnable.
def make_record(mode):
if mode not in {'Review', 'Archive'}:
raise ValueError('mode')
return {'Mode': mode, 'Enabled': True, 'Count': 2}
record = make_record('Review')
assert set(record) == {'Mode', 'Enabled', 'Count'}
print(record['Mode'] + ':' + str(record['Count']))
Review:2
This validates the Python fixture only. It performs no PowerShell invocation, module import, parameter binding, or host operation.
Common errors and fixes
A progress message becomes returned data
- Why it happens
- Diagnostics were written to the success stream.
- How to fix it
- Use the intended diagnostic channel and test all emitted objects.
- Prevent it next time
- Assert output type and count at the function boundary.
return is expected to remove previous output
- Why it happens
- Control flow was confused with accumulated pipeline output.
- How to fix it
- Remove incidental emissions at their source.
- Prevent it next time
- Test helpers added before the return path.
A scope boundary is called rollback
- Why it happens
- Variable visibility was confused with external effect isolation.
- How to fix it
- Design explicit cleanup and recovery for effects.
- Prevent it next time
- Document state changes independently of scope.
Check your understanding
1. A function writes a string and then returns one record. What should the caller expect?
Show worked solution
Both success-stream emissions may reach the caller. return does not retract the earlier string. Move the message to an appropriate diagnostic stream or suppress it deliberately, then test the resulting type and cardinality.
2. Why can a typed parameter still require a domain validation rule?
Show worked solution
Conversion establishes a representation, not business correctness or authority. An integer may be negative when only positive counts are allowed, and a valid string may select an unsupported mode. Define the additional constraints explicitly.
Key takeaways
- Declare input schema and every emitted success object.
- Keep scope, diagnostics, and effect recovery distinct.