Vadilate True or False arguments in function

Welcome Forums General PowerShell Q&A Vadilate True or False arguments in function

Viewing 3 reply threads
  • Author
    Posts
    • #191302
      Participant
      Topics: 4
      Replies: 4
      Points: 83
      Rank: Member

      Hello everyone,

      I will try to describe what I am trying to achieve.
      I have a script for deploying virtual machines on vmware.
      I need to validate each value entered befor prociding with another step in script.

      In most cases was quite easy to validate see example below


      # Getting info about clusters
      Write-Host "Select cluster to deploy new virtual machine" -ForegroundColor Green
      Get-Datacenter | Get-Cluster | Select-Object Name | Format-Table -AutoSize
      do {
      $ClusterToDeployVM = Read-Host "Enter cluster name"
      } while (($ClusterToDeployVM -like "") -or ($ClusterToDeployVM.StartsWith(" ")) -or ($ClusterToDeployVM.EndsWith(" ")) -or ((Get-Cluster).Name -notcontains $ClusterToDeployVM)

      In case of IP configuration, there is much more values at the same time and also there is more things to be checked.
      So I was trying to write function with built-in parameter validation but I do not know, how to extract error codes
      from these built-in validation parameters.
      Here is example (sorry for split but there is some limitation in editor)


      function Test-VMipConfiguration {
      [CmdletBinding()]
      param (
      [Parameter(Mandatory=$true)]
      [ValidateNotNull()]
      [ValidateScript({-not($_.StartsWith(" "))})]
      [ValidateScript({-not($_.EndsWith(" "))})]
      [ValidateScript({($_.StartsWith("10"))})]
      [string]$VMip,
      [Parameter(Mandatory=$true)]
      [ValidateNotNull()]
      [ValidateScript({-not($_.StartsWith(" "))})]
      [ValidateScript({-not($_.EndsWith(" "))})]
      [ValidateScript({($_.StartsWith("255"))})]
      [string]$VMnetmask,
      [Parameter(Mandatory=$true)]
      [ValidateNotNull()]
      [ValidateScript({-not($_.StartsWith(" "))})]
      [ValidateScript({-not($_.EndsWith(" "))})]
      [ValidateScript({($_.EndsWith("254"))})]
      [string]$VMgw,
      [Parameter(Mandatory=$true)]
      [ValidateNotNull()]
      [ValidateScript({-not($_.StartsWith(" "))})]
      [ValidateScript({-not($_.EndsWith(" "))})]
      [ValidateScript({($_.StartsWith("10"))})]
      [string]$VMdns1,
      [Parameter(Mandatory=$true)]
      [ValidateNotNull()]
      [ValidateScript({-not($_.StartsWith(" "))})]
      [ValidateScript({-not($_.EndsWith(" "))})]
      [ValidateScript({($_.StartsWith("10"))})]
      [string]$VMdns2,
      [Parameter(Mandatory=$true)]
      [ValidateNotNull()]
      [ValidateRange(1, 4094)]
      [int]$VMvlan
      )
      begin {
      $VMipOctets = @($VMip.Split('.'))
      if ($VMipOctets.Length -gt 4) {
      Write-Error -Message "Invalid number of octets in IP address" -TargetObject $VMip -Category InvalidArgument
      exit
      } if ($VMipOctets.Length -lt 4) {
      Write-Error -Message "Invalid number of octets in IP address" -TargetObject $VMip -Category InvalidArgument
      exit
      }


      $VMnetmaskOctets = @($VMnetmask.Split('.'))
      if ($VMnetmaskOctets.Length -gt 4) {
      Write-Error -Message "Invalid number of octets in mask address" -TargetObject $VMnetmask -Category InvalidArgument
      exit
      } if ($VMnetmaskOctets.Length -lt 4) {
      Write-Error -Message "Invalid number of octets in mask address" -TargetObject $VMnetmask -Category InvalidArgument
      exit
      }


      $VMgwOctets = @($VMgw.Split('.'))
      if ($VMgwOctets.Length -gt 4) {
      Write-Error -Message "Invalid number of octets in gateway address" -TargetObject $VMgw -Category InvalidArgument
      exit
      } if ($VMgwOctets.Length -lt 4) {
      Write-Error -Message "Invalid number of octets in gateway address" -TargetObject $VMgw -Category InvalidArgument
      exit
      }


      $VMdns1Octets = @($VMdns1.Split('.'))
      if ($VMdns1Octets.Length -gt 4) {
      Write-Error -Message "Invalid number of octets in IP address" -TargetObject $VMdns1 -Category InvalidArgument
      exit
      } if ($VMdns1Octets.Length -lt 4) {
      Write-Error -Message "Invalid number of octets in IP address" -TargetObject $VMdns1 -Category InvalidArgument
      exit
      }


      $VMdns2Octets = @($VMdns2.Split('.'))
      if ($VMdns2Octets.Length -gt 4) {
      Write-Error -Message "Invalid number of octets in IP address" -TargetObject $VMdns2 -Category InvalidArgument
      exit
      } if ($VMdns2Octets.Length -lt 4) {
      Write-Error -Message "Invalid number of octets in IP address" -TargetObject $VMdns2 -Category InvalidArgument
      exit
      }
      }


      process {


      }


      end {


      }
      }

      The problem is, that I need to ask user for input, if the input is not valid, I need to repeat user's input to variables
      again and do the validation process again.
      So the only option that I figured out so far, is to write function with my own validation (no built-in agruments validation)

      Does anyone has an idea for better/other solution?
      Thank you for any feedback

    • #191320
      Participant
      Topics: 1
      Replies: 1632
      Points: 3,074
      Helping Hand
      Rank: Community Hero

      Does anyone has an idea for better/other solution?

      When I need to request a decision from the user I'm used to limit the possible input choices to a predefined list of choices. Either I add an index to a given list and the user picks just the index or I use a gridview wtih the parameter -Passthru.

      In any case, I would avoid giving the user the opportunity to input arbitrary text.

      • #191416
        Participant
        Topics: 4
        Replies: 4
        Points: 83
        Rank: Member

        Hello Olaf, I was trying to make script as flexible as possible (in current state even someone in other company could use it with minimum changes to do).
        I agree that predefined list would be a solid solution to avoid wrong inputs but in environment where clusters, folders and other resources are created and revoked frequently, it would require a lot of effort.
        Thank you for your input.

    • #191323
      Participant
      Topics: 8
      Replies: 1274
      Points: 1,033
      Helping Hand
      Rank: Community Hero

      Use Trim to remove the extra whitespace when prompting:

      # Getting info about clusters
      Write-Host "Select cluster to deploy new virtual machine" -ForegroundColor Green
      #Collect the names one time
      $vms = Get-Datacenter | Get-Cluster | Select-Object Name
      #Show the data
      $vms | Format-Table -AutoSize
      
      do {
          #Wrap your call with Trim to remove any whitespace before or after the input
          $ClusterToDeployVM = (Read-Host "Enter cluster name").Trim()
      
          #Provide context on why you are re-prompting for input
          if ( $vms -contains $ClusterToDeployVM ) {
              '{0} vm already exists, choose another name' -f $ClusterToDeployVM
          }
      
      } while ( $vms -notcontains $ClusterToDeployVM )
      

      For the function params, try to use a [type] so that you are not trying to validate a string with a complex pattern. For something like IP addresses, another option is a regex and use validate pattern for something more specific like a subnet mask. There isn't a need to validate null because the type will most likely cover that, but even listing it as mandatory will ensure a value is passed.

      PS C:\WINDOWS\system32> function Test-It {
          param (
              [Parameter(Mandatory=$true)]
              [System.Net.IPAddress]$ip
          )
          begin {}
          process {}
          end {}
      }
      
      Test-It -Ip one
      Test-It : Cannot process argument transformation on parameter 'ip'. Cannot convert value "one" to type "System.Net.IPAddress". Error: "An invalid IP address was specified."
      At line:11 char:13
      + Test-It -Ip one
      +             ~~~
          + CategoryInfo          : InvalidData: (:) [Test-It], ParameterBindingArgumentTransformationException
          + FullyQualifiedErrorId : ParameterArgumentTransformationError,Test-It
       
      
      PS C:\WINDOWS\system32> Test-It -Ip 1.1.1.1
      
      • #191431
        Participant
        Topics: 4
        Replies: 4
        Points: 83
        Rank: Member

        Hello Rob, thanks for tip! I have completely forgot about Trim option. I see only one problem,
        if variable is empty, it throws an error. I could set $ErrorActionPreference = "SilentlyContinue" but then I would
        lost ability to see all error messages (unless I would probably set it only in parts of script where I would need to
        suppress it and then set it back to default)

        Regarding IP configuration test, I need something that will do the test and based on "True" or "False" statement it will
        continue.
        So I have created one-purpose function see below.

        function Test-VmIpConfiguration {
            [CmdletBinding()]
            param (
                [string]$VMip,
                [string]$VMnetmask,
                [string]$VMgw,
                [string]$VMdns1,
                [string]$VMdns2,
                [int]$VMvlan
            )
            
            begin {
                $VMdns1Value = $VMdns1
                $VMdns2Value = $VMdns2
                $VMipOctets = @($VMip.Split('.'))
                $VMnetmaskOctets = @($VMnetmask.Split('.'))
                $VMgwOctets = @($VMgw.Split('.'))
                $VMdns1Octets = @($VMdns1.Split('.'))
                $VMdns2Octets = @($VMdns2.Split('.'))
            }
            
            process {
                # $VMip test
                if ($VMip -like "") {
                    Write-Error -Message "Argument ""VMip"" is empty" -TargetObject $VMip -Category NotSpecified
                    $VMip = 0
                } elseif (($VMip.StartsWith(" ")) -or ($VMip.EndsWith(" "))) {
                    Write-Error -Message "Argument ""VMip"" contains empty character" -TargetObject $VMip -Category InvalidArgument
                    $VMip = 0
                } elseif (-not $VMip.StartsWith("10")) {
                    Write-Error -Message "Argument ""VMip"" does not start with number ""10"" in first octet" -TargetObject $VMip -Category InvalidArgument
                    $VMip = 0
                } elseif (($VMipOctets.Length -lt 4) -or ($VMipOctets.Length -gt 4)) {
                    Write-Error -Message "Invalid number of octets in IP address" -TargetObject $VMip -Category InvalidArgument
                    $VMip = 0
                } else {
                    $VMip = 1
                }
        
                # $VMnetmask test
                if ($VMnetmask -like "") {
                    Write-Error -Message "Argument ""VMnetmask"" is empty" -TargetObject $VMnetmask -Category NotSpecified
                    $VMnetmask = 0
                } elseif (($VMnetmask.StartsWith(" ")) -or ($VMnetmask.EndsWith(" "))) {
                    Write-Error -Message "Argument ""$VMnetmask"" contains empty character" -TargetObject $VMnetmask -Category InvalidArgument
                    $VMnetmask = 0
                } elseif (-not $VMnetmask.StartsWith("255")) {
                    Write-Error -Message "Argument ""VMnetmask"" does not start with number ""255"" in first octet" -TargetObject $VMnetmask -Category InvalidArgument
                    $VMnetmask = 0
                } elseif (($VMnetmaskOctets.Length -lt 4) -or ($VMnetmaskOctets.Length -gt 4)) {
                    Write-Error -Message "Invalid number of octets in network mask" -TargetObject $VMnetmask -Category InvalidArgument
                    $VMnetmask = 0
                } else {
                    $VMnetmask = 1
                }
        
                # $VMgw test
                if ($VMgw -like "") {
                    Write-Error -Message "Argument ""VMgw"" is empty" -TargetObject $VMgw -Category NotSpecified
                    $VMgw = 0
                } elseif (($VMgw.StartsWith(" ")) -or ($VMgw.EndsWith(" "))) {
                    Write-Error -Message "Argument ""VMgw"" contains empty character" -TargetObject $VMgw -Category InvalidArgument
                    $VMgw = 0
                } elseif (-not $VMgw.EndsWith("254")) {
                    Write-Error -Message "Argument ""VMgw"" does not end with number ""254"" in last octet" -TargetObject $VMgw -Category InvalidArgument
                    $VMgw = 0
                } elseif (($VMgwOctets.Length -lt 4) -or ($VMgwOctets.Length -gt 4)) {
                    Write-Error -Message "Invalid number of octets in gateway address" -TargetObject $VMgw -Category InvalidArgument
                    $VMgw = 0
                } else {
                    $VMgw = 1
                }
        
                # $VMdns1 test
                if ($VMdns1 -like "") {
                    Write-Error -Message "Argument ""VMdns1"" is empty" -TargetObject $VMdns1 -Category NotSpecified
                    $VMdns1 = 0
                } elseif (($VMdns1.StartsWith(" ")) -or ($VMdns1.EndsWith(" "))) {
                    Write-Error -Message "Argument ""VMdns1"" contains empty character" -TargetObject $VMdns1 -Category InvalidArgument
                    $VMdns1 = 0
                } elseif (-not $VMdns1.StartsWith("10")) {
                    Write-Error -Message "Argument ""VMdns1"" does not start with number ""10"" in first octet" -TargetObject $VMdns1 -Category InvalidArgument
                    $VMdns1 = 0
                } elseif (($VMdns1Octets.Length -lt 4) -or ($VMdns1Octets.Length -gt 4)) {
                    Write-Error -Message "Invalid number of octets in DNS address" -TargetObject $VMip -Category InvalidArgument
                    $VMdns1 = 0
                } else {
                    $VMdns1 = 1
                }
        
                # $VMdns2 test
                if ($VMdns2 -like "") {
                    Write-Error -Message "Argument ""VMdns2"" is empty" -TargetObject $VMdns2 -Category NotSpecified
                    $VMdns2 = 0
                } elseif (($VMdns2.StartsWith(" ")) -or ($VMdns2.EndsWith(" "))) {
                    Write-Error -Message "Argument ""VMdns2"" contains empty character" -TargetObject $VMdns2 -Category InvalidArgument
                    $VMdns2 = 0
                } elseif (-not $VMdns2.StartsWith("10")) {
                    Write-Error -Message "Argument ""VMdns2"" does not start with number ""10"" in first octet" -TargetObject $VMdns2 -Category InvalidArgument
                    $VMdns2 = 0
                } elseif (($VMdns2Octets.Length -lt 4) -or ($VMdns2Octets.Length -gt 4)) {
                    Write-Error -Message "Invalid number of octets in DNS address" -TargetObject $VMip -Category InvalidArgument
                    $VMdns2 = 0
                } else {
                    $VMdns2 = 1
                }
        
                # DNS duplicity test
                if ($VMdns1Value -like $VMdns2Value) {
                    Write-Error -Message "Argument ""VMdns1"" and ""VMdns2"" cannot be same" -TargetObject $VMdns1 -Category InvalidResult
                    $VMdnsDuplicity = 0
                } else {
                    $VMdnsDuplicity = 1
                }
        
                # VLAN test
                if ($VMvlan -like "") {
                    Write-Error -Message "Argument ""VMvlan"" is empty" -TargetObject $VMvlan -Category NotSpecified
                    $VMvlan = 0
                } elseif (($VMvlan -lt 1) -or ($VMvlan -gt 4094)) {
                    Write-Error -Message "Argument ""VMvlan"" range is not between 1 and 4096" -TargetObject $VMvlan -Category LimitsExceeded
                    $VMvlan = 0
                } else {
                    $VMvlan = 1
                }
            }
            
            end {
                if (($VMip -eq 0) -or ($VMnetmask -eq 0) -or ($VMgw -eq 0) -or ($VMdns1 -eq 0) -or ($VMdns2 -eq 0) -or ($VMvlan -eq 0) -or ($VMdnsDuplicity -eq 0)) {
                    return $false
                } else {
                    return $true
                }        
            }
        }
        

        So then I can ask user for input until it is correct

        do {
            $NewVMip = Read-Host "Enter new IP address"
            $NewVMmask = Read-Host "Enter subnet mask"
            $NewVMgw = Read-Host "Enter gateway address"
            $NewVMdns1 = Read-Host "Enter DNS1 server address"
            $NewVMdns2 = Read-Host "Enter DNS2 server address"
            $NewVMvlan = Read-Host "Enter VLAN number"
        } until (Test-VmIpConfiguration -VMip $NewVMip -VMnetmask $NewVMmask -VMgw $NewVMgw -VMdns1 $NewVMdns1 -VMdns2 $NewVMdns2 -VMvlan $NewVMvlan)
        
        Get-OSCustomizationSpec $NewVMcustomSpec | Get-OSCustomizationNicMapping | Set-OSCustomizationNicMapping -IpMode UseStaticIP -IpAddress $NewVMip -SubnetMask $NewVMmask -DefaultGateway $NewVMgw -Dns $NewVMdns1, $NewVMdns2
        

        I know there is missing other validation and I guess this is your point to use [System.Net.IPAddress]$ip , to avoid writing validation for number of octets, "0" as first number in octet, etc.
        I will test it and rewrite it
        Thank you very much for answer, it will make script much more clear and reliable.

    • #191437
      Participant
      Topics: 1
      Replies: 1632
      Points: 3,074
      Helping Hand
      Rank: Community Hero

      ... but in environment where clusters, folders and other resources are created and revoked frequently, it would require a lot of effort.

      Just to make clear what I meant: I do create this indexed lists to choose from dynamically from arrays created during the runtime of the script. I do not create them by hand. 😉 ... of course that's a little more effort in advance. But it saves effort you spend to validate the user input.

Viewing 3 reply threads
  • You must be logged in to reply to this topic.