Dh3va
Dh3va

Reputation: 51

PowerShell - Is there a way to create a sort of 'alias' that contains multiple parameter of a validateset?

Good afternoon,

I recently created a PowerShell script to automate and test PowerCLI scripts before running them on vCenters/ESXi hosts.

At the moment, I use a validate set to let the user chose on which server/cluster they want to execute their scripts:

param(
    [string]$script = "help",
    [ValidateSet("localhost", "Server1", "Server2")]
    [string]$server,
    [ValidateSet("DC0_C0", "DC0_C1", "DC0_C2", "Customer1")]
    [string[]]$cluster,
    [switch]$help
)

The issues, is that sometimes when you have big customers, probably they have multiple clusters and it would be more convenient to simply type "customer1" and have the script run on cluster1/2 and 4 instead of manually typing all the clusters:

./viexec vminfo localhost Cluster1,Cluster3,Cluster10

./viexec vminfo localhost Customer1 #And have the script run automatically on all the right clusters.

I already tried using an if that checks the value of the variable $cluster and if it equals "Customer1" then it replace his value with the appropriate clusters, but I don't find this solution elegant. And it's hard to configure/maintain, since the user would need to modify the code, so even better if those parameters could be created from an external config file/user input.

I was also wondering if it was possible to retrieve the param of the validateset from a file/csv to avoid users to customize the main script.ps1, but instead simply replace/write their servers and clusters in a .CSV that populates the validateset parameter.

Hope it is clear..

Regards, Alessandro

Upvotes: 1

Views: 500

Answers (2)

mklement0
mklement0

Reputation: 437373

Here's a proof of concept, which implements an Invoke-VI function that exhibits the desired tab-completion / validation behavior, driven by customer-to-cluster mappings defined in a JSON file (see the other answer for a CSV-based solution).

Note:

  • JSON was chosen as a more flexible alternative to CSV, because the latter would require a fixed number of clusters (columns) per customer. Ultimately, something like YAML or an INI-file would be more convenient to end users, but PowerShell currently lacks built-in support for these formats; see GitHub proposal #3607 and GitHub proposal #9035; however, third-party modules are available in the PowerShell Gallery, such as powershell-yaml and PSIni.

  • A function rather than a script is implemented, because preparatory steps are required before the parameters can be declared with the desired tab-completion and validation functionality. This means that, instead of running your *.ps1 script directly you need to dot-source it (. ./viexec.ps1) and then invoke the Invoke-VI function. Alternatively, define the function as part of an (auto-loading) module.

First, create a sample JSON file that contains 2 customers and their associated clusters:

# Create a sample JSON file that maps customer names to clusters.
# This will serve as the basis for tab-completion / argument validation.
@'
{
  "Customer1": [ "DC0_C0", "DC0_C1", "DC0_C2" ],
  "Customer2": [ "DC1_C0", "DC1_C1" ]
}
'@ > CustomerToClusters.json

The code that builds on it:

# Parse the JSON file, assumed to be located in the same dir.
# as this script.
$customerToClusterMapping = ConvertFrom-Json (Get-Content -Raw $PSScriptRoot/CustomerToClusters.json)

# Build the list of customer names and cluster names across all customers.
[string[]] $customerNames = $customerToClusterMapping.psobject.Properties.Name
[string[]] $allClusters = $customerToClusterMapping.psobject.Properties.Value

function Invoke-VI {
  param(
    # Tab-complete cluster names as well as customer names.
    [ArgumentCompleter({ param($cmd, $param, $wordToComplete) ($customerNames + $allClusters) -like "$wordToComplete*" })]
    # Ensure that only known customer / cluster names were specified.
    # NOTE: If validation fails, the (default) error message is unhelpful.
    #       Unfortunately, this cannot be helped in *Windows PowerShell*, but in
    #       PowerShell (Core) 7+, you can add an `ErrorMessage` property:
    #         [ValidateScript({ $_ -in ($customerNames + $allClusters) }, ErrorMessage = 'Unknown customer/cluster name: {0}')]
    [ValidateScript({ $_ -in ($customerNames + $allClusters) })]
    [string[]] $Cluster
  )

  # Resolve the specified cluster arguments and remove duplicates from the
  # resulting list of cluster-only names.
  $resolvedClusters = $Cluster | ForEach-Object {
    # If a customer name was specified, eturn the list of clusters for the specified customer.
    if ($_ -in $customerNames) { $customerToClusterMapping.$_ } 
    else { $_ }
  } | Select-Object -Unique
  
  "Specified or implied clusters: $resolvedClusters"

}  

Sample call, after having dot-sourced the code above:

PS> Invoke-VI Customer1    # Customer1 was tab-completed.
Specified or implied clusters: DC0_C0 DC0_C1 DC0_C2

Note how the customer name was resolved to the clusters associated with.

Upvotes: 1

mklement0
mklement0

Reputation: 437373

Here's a variant of the JSON-based answer, which:

First, create a sample CSV file that contains 2 customers and their associated clusters:

# Create a sample CSV file that maps customer names to clusters.
# This will serve as the basis for tab-completion / argument validation.
# IMPORTANT: Be sure that you have enough headers (colum names) to cover 
#            the maximum number of columns values.
@'
Customer,Cluster1,Cluster2,Cluster3,Cluster4,Cluster5
Customer1,DC0_C0,DC0_C1,DC0_C2
Customer2,DC1_C0,DC1_C1
'@ > CustomersToClusters.csv

The code that builds on it:

# Parse the CSV file, assumed to be located in the same dir.
# as this script.
$csvRows = Import-Csv $PSScriptRoot/CustomersToClusters.csv

# Build a hashtable of customer names mapped to their associated clusters.
$colCount = $csvRows[0].psobject.Properties.Name.Count
$htCustomersToClusters = [ordered] @{}
foreach ($row in $csvRows) {
  $htCustomersToClusters[$row.Customer] = $row.psobject.Properties.Value[1..($colCount-1)] -ne $null
}
# Build an array of all customer and cluster names.
[string[]] $allCustomersAndClusters = $htCustomersToClusters.Keys + $htCustomersToClusters.GetEnumerator().ForEach({ $_.Value })

# Define the custom class that implements the System.Management.Automation.IValidateSetValuesGenerator
# interface, to be passed to the [ValidateSet()] attribute.
class CustomerAndClusters : System.Management.Automation.IValidateSetValuesGenerator {
  [string[]] GetValidValues() { return $script:allCustomersAndClusters }
}

function Invoke-VI {
  param(
    # Provide tab-completion and validation based on the values
    # returned by a [CustomersAndClusters] instance's .GetValidValues() call.
    [ValidateSet([CustomerAndClusters])]
    [string[]] $Cluster
  )

  # Resolve the specified cluster arguments and remove duplicates from the
  # resulting list of cluster-only names.
  $resolvedClusters = $Cluster | ForEach-Object {
    # If a customer name was specified, resolve it to the list of associated clusters.
    if ($customerClusters = $htCustomersToClusters[$_]) { $customerClusters }
    else { $_ }
  } | Select-Object -Unique
  
  "Specified or implied clusters: $resolvedClusters"

}

Upvotes: 0

Related Questions