Home > database >  ValidateScript and order of operations
ValidateScript and order of operations

Time:02-04

I am not understanding why ValidateScript is not erroring out until the end..

param (
    [Parameter(
        Mandatory = $true,
        HelpMessage = "Specifies the email address that will manage the share drive to be created.")]
        [ValidateScript({
            if ($_.host -eq "domain.com") {
                return $true
            }
            else {
                Throw [System.Management.Automation.ValidationMetadataException] "Enter an email address ending with @domain.com"
            }
        })][Net.Mail.MailAddress]$EmailAddress,
    
    [Parameter(
        Mandatory = $true,
        HelpMessage = "Specifies the name of the share drive name to be created. Enter a shared drive name like prgm- and at least 2 letters. Example prgm-hi")]
    [ValidateScript({
            if ($_ -like "prgm-??*") {
                return $true
            }
            else {
                Throw [System.Management.Automation.ValidationMetadataException] "Enter a shared drive name like prgm- and at least 2 letters. Example prgm-hi"
            }
        })][string]$SharedDriveName
)

In this code snip sample above if the script is run the person will get past the emailaddress variable if it is wrong and than get to the error after all the variables are answered.

I am not understanding how to have the script stop right as someone makes a bad entry.

I assume, I will have to use something other than validate script but I am not sure what?

CodePudding user response:

Perhaps surprisingly, when PowerShell automatically prompts for values for missing mandatory parameters (written as of PowerShell 7.3.2):

  • it enforces the parameter data type after each prompt.
  • but performs validation - based on attributes such as ValidateScript() - only after ALL prompts have been answered.

This makes for an awkward interactive experience, given that as a user you won't learn whether any of your inputs are valid until all of them have been provided.

I can't speak to the design intent - a conceivable reason for this evaluation order is to allow validation script blocks to also consider other parameter values; however, in practice you can not refer to other parameters inside a [ValidateScript()] script block.

On a general note, the automatic prompting mechanism has fundamental limitations - see GitHub issue #7093.


Workaround:

You can move prompting and validating into the body of your function (as well), where you get to control the execution flow - obviously, this requires extra work:

param (
  [Parameter(
    # Do NOT make the parameter mandatory, so that it can be prompted for
    # in the function body, but DO define a HelpMessage.
    HelpMessage = "Specify the email address that will manage the share drive to be created (must end in @sonos.com)"
  )]
  [ValidateScript({
      if ($_.host -eq 'sonos.com') {
        return $true
      }
      else {
        Throw "Enter an email address ending with @sonos.com"
      }
  })]
  [Net.Mail.MailAddress] $EmailAddress,
    
  [Parameter(
    # Do NOT make the parameter mandatory, so that it can be prompted for
    # in the function body, but DO define a HelpMessage.
    HelpMessage = "Specify the name of the share drive name to be created. Enter a shared drive name like prgm- and at least 2 letters. Example prgm-hi"
  )]
  [ValidateScript({
      if ($_ -like "prgm-??*") {
        return $true
      }
      else {
        Throw "Enter a shared drive name like prgm- and at least 2 letters. Example prgm-hi"
      }
  })]
  [string]$SharedDriveName
)

# If no values were provided any of the conceptually mandatory parameters,
# prompt for and validate them now.
# Infer which parameters are conceptually mandatory from the presence of a 
# .HelpMessage property.
$MyInvocation.MyCommand.Parameters.GetEnumerator() | ForEach-Object {
  # See if there's a .HelpMessage property value and, if so,
  # check if the parameter hasn't already been bound.
  $helpMessage = $_.Value.Attributes.Where({ $_ -is [Parameter] }).HelpMessage
  if ($helpMessage -and -not $PSBoundParameters.ContainsKey($_.Key)) {
    # Prompt now and validate right away, which happens implicitly when
    # the parameter variable is assigned to.
    while ($true) {
      try {
        Set-Variable -Name $_.Key -Value (Read-Host $helpMessage)
        break # Getting here means that validation succeeded -> exit loop.
      }
      catch { 
        # Validation failed: write the error message as a warning, and keep prompting.
        Write-Warning "$_"
      }
    }
  }
}

# ... all required arguments are now present and validated.

Note:

  • The upside of this approach is that you can keep prompting until valid input is received (or the user aborts with Ctrl-C).
  • Related