Author Posts

June 2, 2016 at 6:00 pm

I'm having a problem making parameter sets work the way I want.

My main function:

function Get-ComputerName {
    [CmdletBinding(DefaultParameterSetName = 'AllComputers')]
    [OutputType([String])]

    Param(
        # Gets all computers: workstations, member servers, and domain controllers. This is the default behavior when no switch is specified.
        [Parameter(ParameterSetName="AllComputers")]
        [switch]$AllComputers,

        # Lists all workstations.
        [Parameter(ParameterSetName="SomeComputers")]
        [switch]$Workstations,

        # Lists all member servers.
        [Parameter(ParameterSetName="SomeComputers")]
        [Alias('Servers')]
        [switch]$MemberServers,

        # Lists all domain controllers.
        [Parameter(ParameterSetName="SomeComputers")]
        [Alias('DCs')]
        [switch]$DomainControllers,

        # Filters the list of computers to include only those currently online. May not be used with -Offline.
        [Parameter()]
        [switch]$Online,

        # Filters the list of computers to include only those currently offline. May not be used with -Online.
        [Parameter()]
        [switch]$Offline
    )
    
    BEGIN {}
    PROCESS{
        # Query AD. Clean up @PSBoundParameters and call Get-OnlineComputerName if needed.
    }
    END {}
} #function Get-ComputerName 

This function returns a list of computer names from Active Directory queries. If the -Online or -Offline parameters are supplied then this function calls Get-OnlineComputerName to test network connectivity:

function Get-OnlineComputerName {
    [CmdletBinding(DefaultParameterSetName = 'Online')]
    [OutputType([String])]
    param (
        # Used to specify the computers of interest.
        [Parameter(Mandatory=$true,
            ValueFromPipeline=$true, 
            ValueFromPipelineByPropertyName=$true,
            Position=0)]
        [string[]]$ComputerName,
    
        # Returns a list of online computers.
        [Parameter(ParameterSetName="Online")]
        [switch]$Online,

        # Returns a list of offline computers.
        [Parameter(ParameterSetName="Offline")]
        [switch]$Offline
    )

    BEGIN {}
    PROCESS{
        # Return list of computers that passed or failed Test-Connection
    }
    END {}

Help for Get-OnlineComputerName shows the desired syntax:
Get-OnlineComputerName [-ComputerName] {string[]} [-Online] [{CommonParameters}]
Get-OnlineComputerName [-ComputerName] {string[]} [-Offline] [{CommonParameters}]

Help for Get-ComputerName shows this syntax:
Get-ComputerName [-AllComputers] [-Online] [-Offline] [{CommonParameters}]
Get-ComputerName [-Workstations] [-MemberServers] [DomainControllers] [-Online] [-Offline] [{CommonParameters}]

I'd like to have Get-ComputerName accept -Online or -Offline, but not both, and to also have the option of using neither to get a computer name list without calling Get-OnlineComputerName to test connectivity. The closest I could get using parameter sets was to make -Online and -Offline mutually exclusive but the function then won't let me not use one of them.

My options without parameters sets seem to be let Get-ComputerName pass both -Online and -Offline, at which point Get-OnlineComputername writes an error (not ideal since the user didn't call that function and doesn't know why it's giving an error) or use a Throw in Get-ComputerName when both -Online and -Offline have been used.

Is there a cleaner approach?

  • This topic was modified 2 years, 3 months ago by  Mike Hillis.

June 2, 2016 at 6:09 pm

So, I think I'd create a -Mode switch that accepted either Online or Offline, using a ValidateSet() to control that. Make the parameter non-mandatory, so it's also legal to not use it at all.

Mutually exclusive multi-set switches are a bit hard. The way you've coded your first function, both -Online and -Offline are in all parameter sets. To do what you want, you'd have to create more parameter sets. One that takes -AllComputers and -Online, one that takes -AllComputers and -Offline, one that takes... you see where I'm going.

Look at Where-Object. It has a zillion parameter sets for that exact reason. It's inelegant, but in that case it's how they hacked together the "easy syntax" option.

June 2, 2016 at 6:28 pm

In your Get-OnlineComputerName function, set a default parameter set of something other than 'Online' or 'Offline'. Doesn't matter what it's called. That'll tell the parameter binding engine how to behave if neither switch is used.

June 2, 2016 at 7:19 pm

Where's the and splatting part? 🙂

June 2, 2016 at 8:01 pm

Here's the splatting part:

        elseif ($Online -or $Offline) {
            Write-Verbose "Sending computer name list to Get-OnlineComputerName"
            $PSBoundParameters.Remove('AllComputers') | Out-Null
            $PSBoundParameters.Remove('Workstations') | Out-Null
            $PSBoundParameters.Remove('MemberServers') | Out-Null
            $PSBoundParameters.Remove('DomainControllers') | Out-Null
            Get-OnlineComputerName $FormattedList @PSBoundParameters
            Write-Verbose "Finished call to Get-OnlineComputerName"
        } else {

June 2, 2016 at 8:57 pm

Solved: I went with the -Mode option, which prevents the -Online/-Offline conflict that was bothering me plus gives me tab completion for the -Mode arguments. Thanks for the help.

My modified main function:

function Get-ComputerName {
    [CmdletBinding(DefaultParameterSetName = 'AllComputers')]
    [OutputType([String])]

    Param(
        # Gets all computers: workstations, member servers, and domain controllers. This is the default behavior when no switch is specified.
        [Parameter(ParameterSetName='AllComputers')]
        [switch]$AllComputers,

        # Lists all workstations.
        [Parameter(ParameterSetName='SomeComputers')]
        [switch]$Workstations,

        # Lists all member servers.
        [Parameter(ParameterSetName='SomeComputers')]
        [Alias('Servers')]
        [switch]$MemberServers,

        # Lists all domain controllers.
        [Parameter(ParameterSetName='SomeComputers')]
        [Alias('DCs')]
        [switch]$DomainControllers,

        # Filters the list of computers to only those currently online or offline.
        [Parameter(Position=0)]
        [ValidateSet('Online','Offline')]
        [string]$Mode
    )

My modified called function:

function Get-OnlineComputerName {
    [CmdletBinding()]
    [OutputType([String])]
    param (
        # Specify the computers of interest.
        [Parameter(Mandatory=$true,
            ValueFromPipeline=$true, 
            ValueFromPipelineByPropertyName=$true,
            Position=0)]
        [string[]]$ComputerName,
    
        # List either online or offline computers.
        [Parameter(Position=1)]
        [ValidateSet('Online','Offline')]
        [string]$Mode = 'Online'
    )

Typical usage for Get-ComputerName:
Get-ComputerName -Workstations Online

There's a remaining minor issue. If the function includes comment based help, and my functions do, they the help syntax doesn't list the Online/Offline arguments for the -Mode parameter. If I leave off the comment based help then the auto-generated help syntax lists the Online/Offline arguments in the help syntax. Is there a way around that quirk to get the validate set arguments to list in the help syntax when using comment based help?

  • This reply was modified 2 years, 3 months ago by  Mike Hillis.
  • This reply was modified 2 years, 3 months ago by  Mike Hillis.