Reject parameters not in Param() statement - powershell

I have a powershell script that starts with
Param([switch]$NoDownload, [switch]$NoUnpack, [switch]$NoExtract, [switch]$NoImport, [switch]$NoBackup)
and I was very happy because I thought that it would provide automatic parameter validation. Until one day I made a mistake and wrote:
powershell -f myscript.ps1 -NoDowload
(notice the lack of n), and it happily downloaded something I didn't want it to.
How do I tell the powershell parameter handling machinery that the only valid parameters are the ones I explicitly state in the Param statement?

Add a CmdletBinding attribute to the param() block - this makes PowerShell treat your script/function like a cmdlet - rather than a "simple" function - and it will apply much more rigorous parameter binding validation, including throwing errors when you attempt to bind a non-existing parameter name:
[CmdletBinding()]
param(
[switch]$NoDownload,
[switch]$NoUnpack,
[switch]$NoExtract,
[switch]$NoImport,
[switch]$NoBackup
)
PS ~> .\myScript.ps1 -NoDowload
myScript.ps1 : A parameter cannot be found that matches parameter name 'NoDowload'.
At line:1 char:16
+ .\myScript.ps1 -NoDowload
+ ~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [myScript.ps1], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : NamedParameterNotFound,myScript.ps1
Adding an explicit [Parameter()] attribute to any defined parameter will also implicitly make PowerShell treat your script/function as "advanced", even in the absence of a [CmdletBinding()] attribute:
param(
[switch]$NoDownload,
[switch]$NoUnpack,
[switch]$NoExtract,
[switch]$NoImport,
[switch]$NoBackup,
[Parameter(Mandatory = $false, DontShow = $true)]
$DummyParameterThatTriggersCmdletBinding
)

Related

Separating values entered into a string

So I am trying to create a Powershell menu that when the user selects a choice, it will ask for the value or values its trying to search (ex. Ping Multiple Computers). I am currently having a hard time getting that to work. I will post pictures to show what I mean
When I type in one name to search the command executes fine shown below:
When I try with multiple values it doesn't work:
Here is a snap of the code I have:
Any help of course is much appreciated.
UPDATE - 11/13
This is what I currently have:
function gadc {
Param(
[Parameter(Mandatory=$true)]
[string[]] $cname # Note: [string[]] (array), not [string]
)
$cname = "mw$cname"
Get-ADComputer $cname
}
This is the output in the Console
cmdlet gadc at command pipeline position 1
Supply values for the following parameters:
cname[0]: imanuel
cname[1]: troyw
cname[2]: hassan
cname[3]:
Get-ADComputer : Cannot convert 'System.String[]' to the type
'Microsoft.ActiveDirectory.Management.ADComputer' required by parameter 'Identity'. Specified
method is not supported.
At line:32 char:19
+ Get-ADComputer $cname
+ ~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Get-ADComputer], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.ActiveDirectory.Management.Commands.G
etADComputer
Press Enter to continue...:
**And here is the other way with the same result:**
cmdlet gadc at command pipeline position 1
Supply values for the following parameters:
cname[0]: imanuel, troyw
Get-ADComputer : Cannot convert 'System.String[]' to the type
'Microsoft.ActiveDirectory.Management.ADComputer' required by parameter 'Identity'. Specified
method is not supported.
At line:32 char:19
+ Get-ADComputer $cname
+ ~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Get-ADComputer], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.ActiveDirectory.Management.Commands.G
etADComputer
Press Enter to continue...:
You need to declare your mandatory parameter as an array, then PowerShell's automatic prompting will allow you to enter multiple values, one by one - simply press Enter by itself after having submitted the last value in order to continue:
function gadc {
param(
[Parameter(Mandatory)]
[string[]] $cname # Note: [string[]] (array), not [string]
)
# Get-ADComputer only accepts one computer name at a time
# (via the positionally implied -Identity parameter), so you must loop
# over the names.
# The following should work too, but is slightly slower:
# $cname | Get-ADComputer
foreach ($c in $cname) { Get-ADComputer $c }
}

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).

PS 4.0 Parameters with Space - Catch Error

I'm executing script in batch and passing 5 parameters separated by a comma via Java. PS 4.0 accepts parameters without the "".
[CmdletBinding()]
Param (
[Parameter(Position = 0)]
[string[]] $inpParms = $(throw 'Failure : This is required.')
)
I invoke the script like
myScript.ps1 user,pwd,Server Name, DLName,Action
It errors out due to the space in "Server Name". Throws error which spits back the parameters:
A positional parameter cannot be found that accepts argument 'System.Object[]'.
At line:1 char:1
+ .\myScript.ps1 user,pwd,Server Name, DLName,Create
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [ExchangeDL.ps1], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,ExchangeDL.ps1
Is there a way to trap this error so I don't spit sensitive info in logs?
While you don't have to quote arguments that don't have shell metacharacters in them, a space is a metacharacter, so you have two choices:
`-quote the metacharacters individually:
./myScript.ps1 user, pwd, Server` Name, DLName, Create
Or use "..." (interpolating) or '...' (literal) to enclose entire arguments as needed:
./myScript.ps1 user, pwd, 'Server Name', DLName, Create
Generally, though, your script will be more maintainable if you define individual parameters rather than a single array.
If you want to catch an incorrect invocation attempt, use a Try / Catch statement:
Try {
./myScript.ps1 user, pwd, Server Name, DLName, Action
} Catch {
Throw "Invocation of ./myScript.ps1 failed."
}
By not using the information from the statement-terminating error that the incorrect invocation produced (accessible as $_ in the Catch block) in the Throw statement, the original command line is not leaked.

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"

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
)