Parameter validateset wildcard - powershell

Is it possible to make a Paramater validateset work with a wildcard?
I would want on the * places to accept 0-100.
param
(
[Parameter(Mandatory=$True)]
[validateset("6.1.*.*")]
[string]$variable
)
Error message:
Cannot validate argument on parameter 'variable'. The argument "6.1.1.0" does not belong to the set "6.1.." specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again.
+ CategoryInfo : InvalidData: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : ParameterArgumentValidationError

Since it looks like you're looking to validate a version, you may want to declare the parameter of the type [version] and use the ValidateScript attribute to validate the value rather than using string matching:
function Test-Version {
param(
[ValidateScript({
$_.Major -eq '6' -and
$_.Minor -eq '1' -and
$_.Build -in (0..100) -and
$_.Revision -in (0..100) -or
$(throw 'Wrong Version')
})]
[version]$Version
)
}

No, that's what [ValidatePattern()] is for:
param(
[Parameter(Mandatory=$True)]
[ValidatePattern('6\.1\.\d{1,3}\.\d{1,3}')]
[string]$variable
)
It takes a regular expression as the parameter.
[ValidateSet()] is meant to be used if there is a small, constant set of values. PowerShell also provides autocompletion for these. For example:
[ValidateSet('Windows', 'Mac', 'Linux')
$OperatingSystem
See this article for more parameter validation attributes.

Related

ValidateScript unexpectedly returning false when condition is true

I have a PowerShell function I'm writing to build and execute a variety of logman.exe commands for me so I don't have to reference the provider GUIDs and type up the command each time I want to capture from a different source. One of the parameters is the file name and I am performing some validation on the parameter. Originally I used -match '.+?\.etl$' to check that the file name had the .etl extension and additionally did some validation on the path. I later decided to remove the path validation but neglected to change the validation attribute to ValidatePattern.
What I discovered was that while it worked perfectly on the machine I was using to author and validate it, on my Server 2016 Core machine it seemed to misbehave when calling the function but that if I just ran the same check at the prompt it worked as expected.
The PowerShell:
[Parameter(ParameterSetName="Server", Mandatory=$true)]
[Parameter(ParameterSetName="Client", Mandatory=$true)]
[ValidateScript({$FileName -match '.+?\.etl$'}]
[string] $FileName = $null
The Output:
PS C:\Users\Administrator> Start-TBLogging -ServerLogName HTTPSYS -FileName ".\TestLog.etl"
PS C:\Users\Administrator> Start-TBLogging : Cannot validate argument on parameter 'FileName'. The "$FileName -match '.+?\.etl$'" validation script
for the argument with value ".\TestLog.etl" did not return a result of True. Determine why the validation script failed,
and then try the command again.
At line:1 char:50
+ Start-TBLogging -ServerLogName HTTPSYS -FileName ".\TestLog.etl"
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Start-TBLogging], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Start-TBLogging
Trying it manually worked:
PS C:\Users\Administrator> $FileName = ".\TestLog.etl"
PS C:\Users\Administrator> $FileName -match '.+?\.etl$'
True
After changing the function to use ValidatePattern it works just fine everywhere but I was wondering if anyone could shed light on the discontinuity.
As Joshua Shearer points out in a comment on a question, you must use automatic variable $_ (or its alias form, $PSItem), not the parameter variable to refer to the argument to validate inside [ValidateScript({ ... })].
Therefore, instead of:
# !! WRONG: The argument at hand has NOT yet been assigned to parameter
# variable $FileName; by design, that assignment
# doesn't happen until AFTER (successful) validation.
[ValidateScript({ $FileName -match '.+?\.etl$' }]
[string] $FileName
use:
# OK: $_ (or $PSItem) represents the argument to validate inside { ... }
[ValidateScript({ $_ -match '.+?\.etl$' })]
[string] $FileName
As briantist points out in another comment on the question, inside the script block $FileName will have the value, if any, from the caller's scope (or its ancestral scopes).

Check whether a variable is null

I'd like to check if a variable is null:
function send_null_param ([ref]$mycredentials){
if (! $mycredentials) {
Write-Host 'Got no credentials'
$mycredentials = Get-Credential 'mydomain.com\myuserid'
} else {
Write-Host 'Got credentials'
}
}
$myidentifier = $null
send_null_param ([ref]$myidentifier)
This code is based on:
https://www.thomasmaurer.ch/2010/07/powershell-check-variable-for-null/,
but this does not work.
How can I fix this?
ps. There is something in Stack Overflow for a string being null but not something more generic:
Check if a string is not NULL or EMPTY
Since you're trying to assign $myCredential with Get-Credential in the case of it being absent, then I assume you want your parameter to be a [PSCredential].
In that case, strongly type your parameter, and mark it as mandatory (by the way [ref] is not needed at all :
function Get-MyCredential {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[PSCredential]
$Credential
)
Write-Host "Got credential with username '$($Credential.Username)'"
}
This way, you really don't have to do any checking at all. Making it Mandatory lets PowerShell enforce that for you, and making it a [PSCredential] ensures that the object is a valid [PSCredential] from the start.
The only other case you might want to check for, depending on what you're doing with the credential, is an empty credential.
To do that you can compare it to [PSCredential]::Empty, and you can do it in a validation attribute so it gets done on parameter binding:
function Get-MyCredential {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[PSCredential]
[ValidateScript( {
$_ -ne [PSCredential]::Empty
} )
$Credential
)
Write-Host "Got credential with username '$($Credential.Username)'"
}
You can do other validation in there if you want (checking for a certain username format, like if it needs to be an email address or something). If it's complex it may be better done within the function body, depends on the scenario.
But for the most part you probably don't need additional validation at all.
This works as intended. You are using a [ref] in your parameter. You can think of it as a pointer. And if you pass a variable to a pointer, the pointer will contain the address of the variable. The value doesn't matter.
A [ref] isn't a pointer, but the concept is It is an object of the type 'System.Management.Automation.PSReference'.
An object of the type PSReference saves the actual Value of the object you're referencing under the property 'Value' and when the function is complete it will save the value back to the original variable.
Your code would work if you use the 'Value'-Property of the 'mycredentials' variable in your if-statement:
function send_null_param ([ref]$mycredentials){
if (! $mycredentials.Value) {
Write-host 'Got no credentials'
$mycredentials = Get-Credential 'mydomain.com\myuserid'
}
else {Write-host 'Got credentials'}
}
$myidentifier=$null
send_null_param ([ref]$myidentifier)
I agree with briantist if there is no special reason you shouldn't be using a [ref].
Add the param block to your function and make it mandatory.
Function New-Creds
{
[CmdletBinding()]
[Alias('nc')]
Param
(
[Parameter(Mandatory=$true,
HelpMessage = 'This is a required field. It cannot be blank')]$MyCredentials
)
# Code begins here
$MyCredentials
}
Results
New-Creds -MyCredentials
New-Creds : Missing an argument for parameter 'MyCredentials'. Specify a parameter of type 'System.Object' and try again.
At line:1 char:11
+ New-Creds -MyCredentials
+ ~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-Creds], ParameterBindingException
+ FullyQualifiedErrorId : MissingArgument,New-Creds
New-Creds
cmdlet New-Creds at command pipeline position 1
Supply values for the following parameters:
(Type !? for Help.)
MyCredentials: !?
This is a required field. It cannot be blank
MyCredentials: SomeCreds
SomeCreds
New-Creds -MyCredentials AnotherCred
AnotherCred

Powershell ParameterSet error when two or more parameters are used

I have a script that prints out log information, basically text. The script has 3 parameters: 'ID', 'Text' and 'Day', where I could filter the information based on those parameters. It's like this:
function Get-Log{
[CmdletBinding(DefaultParameterSetName="All")]
Param(
[Parameter(
ParameterSetName="ByID"
)]
[Int]$ID,
[Parameter(
ParameterSetName="ByText"
)]
[String]$Text,
[Parameter(
ParameterSetName="ByDay"
)]
[String]$Day
)
...
}
It works fine if I call the function with no or only one parameter, Ex.:
Get-Log
Get-Log -Text "sample"
But when I use two or more parameters, like:
Get-Log -Text "sample" -Day "29/04/2016"
It spills out parameter set error:
Get-Log : Parameter set cannot be resolved using the specified named parameters
+ get-log -Text "sample" -Day "29/04/2016"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Get-Change], ParameterBind
ingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,Get-Change
Is there a way to specify a different parameter set used when 2 or more parameters are specified? Or maybe a better way to do that?
The way I use parameter sets is to define one "mode" of the script along with the parameters that "mode" would have.
Example, the RunOnce param set has two mandatory parameters, Server and Date.
[Parameter(Mandatory=$false,ParameterSetName="RunOnce",
HelpMessage="Enter ServerName to schedule reboot for.")]
[switch]$RunOnce,
[Parameter(Mandatory=$true,ParameterSetName="RunOnce")]
[string]$server,
[Parameter(Mandatory=$true,ParameterSetName="RunOnce")]
[string]$date,
Used as such Function -RunOnce -Server "blah" -Date "04/29/2016 18:20"

In PowerShell, can Test-Path (or something else) be used to validate multiple files when declaring a string array parameter

I have a function that accepts a string array parameter of files and I would like to use Test-Path (or something else) to ensure that all the files in the string array parameter exists. I would like to do this in the parameter declaration if possible.
Is this possible?
You can use ValidateScript
param(
[parameter()]
[ValidateScript({Test-Path $_ })]
[string[]]$paths
)
For more documentation on parameter validation visit about_Functions_Advanced_Parameters
You can set the parameter to use a validation script like this:
Function DoStuff-WithFiles{
Param([Parameter(Mandatory=$true,ValueFromPipeline)]
[ValidateScript({
If(Test-Path $_){$true}else{Throw "Invalid path given: $_"}
})]
[String[]]$FilePath)
Process{
"Valid path: $FilePath"
}
}
It is recommended to not justpass back $true/$false as the function doesn't give good error messages, use a Throw instead, as I did above. Then you can call it as a function, or pipe strings to it, and it will process the ones that pass validation, and throw the error in the Throw statement for the ones that don't pass. For example I will pass a valid path (C:\Temp) and an invalid path (C:\Nope) to the function and you can see the results:
#("c:\temp","C:\Nope")|DoStuff-WithFiles
Valid path: c:\temp
DoStuff-WithFiles : Cannot validate argument on parameter 'FilePath'. Invalid path given: C:\Nope
At line:1 char:24
+ #("c:\temp","C:\Nope")|DoStuff-WithFiles
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (C:\Nope:String) [DoStuff-WithFiles], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,DoStuff-WithFiles
Edit: I partially retract the Throw comment. Evidently it does give descriptive errors when the validation fails now (thank you Paul!). I could have sworn it (at least used to) just gave an error stating that it failed the validation and left off what it was validating and what it was validating against. For more complex validation scripts I would still use Throw though because the user of the script may not know what $_ -match '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' means if the error throws that at them (validating IPv4 address).
As #Matt said. Test-Path already accepts pipeline input, so you really just have to send the array directly in:
#($path1, $path2) | Test-Path
Which then returns:
> #("C:\foo", "C:\Windows") | Test-Path
False
True
If you just want to know if ALL of them exist:
($pathArray | Test-Path) -notcontains $false
Which yields:
False

Powershell: Args/params not being populated

I have a PowerShell script:
param (
[Parameter(Mandatory=$true)][string]$input,
[Parameter(Mandatory=$true)][string]$table
)
Write-Host "Args:" $Args.Length
Get-Content $input |
% { [Regex]::Replace($_, ",(?!NULL)([^,]*[^\d,]+[^,]*)", ",'`$1'") } |
% { [Regex]::Replace($_, ".+", "INSERT INTO $table VALUES (`$1)") }
The Write-Host part is for debugging.
I run it as .\csvtosql.ps1 mycsv.csv dbo.MyTable (from powershell shell), and get
Args: 0
Get-Content : Cannot bind argument to parameter 'Path' because it is an empty s
tring.
At C:\temp\csvtosql.ps1:7 char:12
+ Get-Content <<<< $input |
+ CategoryInfo : InvalidData: (:) [Get-Content], ParameterBinding
ValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAl
lowed,Microsoft.PowerShell.Commands.GetContentCommand
I get exactly the same error with any parameters that I pass, also the same error if I try to use named parameters.
What can cause parameters not to be passed in?
UPDATE: PowerShell ISE asks me for these parameters using GUI prompts, then gives me the same error about them not being passed in.
Unless you marked a parameter with the ValueFromRemainingArguments attribute (indicates whether the cmdlet parameter accepts all the remaining command-line arguments that are associated with this parameter), Args is "disabled". If all you need is the arguments count call the special variable:
$PSBoundParameters.Count
Do not mix. Make use of $args or parameters.
Also do note that $input is a special variable, don't declare it as a parameter. http://dmitrysotnikov.wordpress.com/2008/11/26/input-gotchas/
You're calling your script with positional parameters (i.e. unnamed) and PowerShell doesn't know how to map them to your script parameters. You need to either call your script using the parameter names:
.\csvtosql.ps1 -input mycsv.csv -table dbo.MyTable
or update your script to specify your preferred order of positional parameters:
param (
[Parameter(Mandatory=$true,Position=0)]
[string]
$input,
[Parameter(Mandatory=$true,Position=1)]
[string]
$table
)