When designing PowerShell functions with multiple connection methods, we often need to enforce these rules:
- Exactly one connection identifier (ComputerName or IPAddress) must be specified
- Credentials must be complete when provided (both username and password)
Here's the proper way to structure the parameter sets:
[CmdletBinding(DefaultParameterSetName="ComputerName")]
param (
# ComputerName parameter set
[Parameter(Mandatory=$true, ParameterSetName="ComputerName")]
[Parameter(Mandatory=$true, ParameterSetName="ComputerNameCred")]
[string]$ComputerName,
# IPAddress parameter set
[Parameter(Mandatory=$true, ParameterSetName="IPAddress")]
[Parameter(Mandatory=$true, ParameterSetName="IPAddressCred")]
[string]$IPAddress,
# Credential parameters
[Parameter(Mandatory=$true, ParameterSetName="ComputerNameCred")]
[Parameter(Mandatory=$true, ParameterSetName="IPAddressCred")]
[string]$AdminUser,
[Parameter(Mandatory=$true, ParameterSetName="ComputerNameCred")]
[Parameter(Mandatory=$true, ParameterSetName="IPAddressCred")]
[SecureString]$AdminPassword
)
The key is creating distinct groups:
- Base connection parameters (ComputerName/IPAddress only)
- Credential variants (when authentication is needed)
Add this begin block to enforce mutual exclusion:
begin {
if ($PSBoundParameters.ContainsKey('ComputerName') -and
$PSBoundParameters.ContainsKey('IPAddress')) {
throw "Cannot specify both ComputerName and IPAddress parameters"
}
if (($PSBoundParameters.ContainsKey('AdminUser') -xor
$PSBoundParameters.ContainsKey('AdminPassword'))) {
throw "Must specify both AdminUser and AdminPassword or neither"
}
}
Valid invocations:
# ComputerName only
Connect-Server -ComputerName "SRV01"
# IPAddress only
Connect-Server -IPAddress "192.168.1.100"
# With credentials
Connect-Server -ComputerName "SRV01" -AdminUser "admin" -AdminPassword (ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force)
Invalid invocations that will fail:
# Both identifiers (will throw)
Connect-Server -ComputerName "SRV01" -IPAddress "192.168.1.100"
# Missing password (will throw)
Connect-Server -ComputerName "SRV01" -AdminUser "admin"
For more complex scenarios, consider dynamic parameters:
dynamicparam {
$runtimeParams = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
if (!$PSBoundParameters.ContainsKey('ComputerName') -and
!$PSBoundParameters.ContainsKey('IPAddress')) {
$attrib = New-Object System.Management.Automation.ParameterAttribute
$attrib.Mandatory = $true
$attrib.ParameterSetName = "DynamicComputerName"
$attribColl = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$attribColl.Add($attrib)
$runtimeParams.Add("ComputerName",
New-Object System.Management.Automation.RuntimeDefinedParameter(
"ComputerName", [string], $attribColl))
}
return $runtimeParams
}
When designing robust PowerShell functions, parameter sets provide a powerful way to create mutually exclusive parameter groups. However, configuring them to enforce complex validation rules can be tricky. Let's examine a complete solution for enforcing these requirements:
- Either ComputerName OR IPAddress must be specified (mandatory)
- These parameters should be mutually exclusive (only one can be used)
- If either AdminUser or AdminPassword is provided, both become mandatory
Here's the optimized version of the function with proper parameter sets:
function Connect-Target {
[CmdletBinding(DefaultParameterSetName='ComputerName')]
param (
# Parameter set for computer name without credentials
[Parameter(Mandatory=$true, ParameterSetName='ComputerName')]
[Parameter(Mandatory=$true, ParameterSetName='ComputerNameWithCred')]
[string]$ComputerName,
# Parameter set for IP address without credentials
[Parameter(Mandatory=$true, ParameterSetName='IPAddress')]
[Parameter(Mandatory=$true, ParameterSetName='IPAddressWithCred')]
[string]$IPAddress,
# Credential parameters (mutually dependent)
[Parameter(Mandatory=$true, ParameterSetName='ComputerNameWithCred')]
[Parameter(Mandatory=$true, ParameterSetName='IPAddressWithCred')]
[string]$AdminUser,
[Parameter(Mandatory=$true, ParameterSetName='ComputerNameWithCred')]
[Parameter(Mandatory=$true, ParameterSetName='IPAddressWithCred')]
[securestring]$AdminPassword
)
# Validate that only one connection method is used
if ($PSBoundParameters.ContainsKey('ComputerName') -and $PSBoundParameters.ContainsKey('IPAddress')) {
throw "Cannot specify both ComputerName and IPAddress parameters"
}
# Rest of function implementation...
}
The solution uses these techniques:
- Two primary parameter sets for connection methods (ComputerName/IPAddress)
- Two secondary sets that include credential parameters
- Explicit validation for mutual exclusion
- Mandatory=$true on all primary parameters in their respective sets
Valid invocations:
# Using computer name without credentials
Connect-Target -ComputerName 'SERVER01'
# Using IP address with credentials
$securePass = ConvertTo-SecureString 'P@ssw0rd' -AsPlainText -Force
Connect-Target -IPAddress '192.168.1.100' -AdminUser 'admin' -AdminPassword $securePass
Invalid invocations that will be blocked:
# Missing required parameter (will prompt or fail)
Connect-Target
# Both connection methods specified (our custom validation catches this)
Connect-Target -ComputerName 'SERVER01' -IPAddress '192.168.1.100'
# Missing credential component
Connect-Target -ComputerName 'SERVER01' -AdminUser 'admin'
For better security practices, consider using [PSCredential] instead of separate username/password:
function Connect-Target {
[CmdletBinding(DefaultParameterSetName='ComputerName')]
param (
[Parameter(Mandatory=$true, ParameterSetName='ComputerName')]
[Parameter(Mandatory=$true, ParameterSetName='ComputerNameWithCred')]
[string]$ComputerName,
[Parameter(Mandatory=$true, ParameterSetName='IPAddress')]
[Parameter(Mandatory=$true, ParameterSetName='IPAddressWithCred')]
[string]$IPAddress,
[Parameter(Mandatory=$true, ParameterSetName='ComputerNameWithCred')]
[Parameter(Mandatory=$true, ParameterSetName='IPAddressWithCred')]
[pscredential]$Credential
)
# Implementation would use $Credential.GetNetworkCredential()
# for username/password when needed
}