Same Parameter name, different scope, same value. How to deal? - powershell

# cmdlet MyCmdlet.ps1
Param(
[Parameter(Mandatory=$true)]
[string]$Variable1,
[string]$Variable2
)
Begin {
Function Function-Name {
Param (
[Parameter(Mandatory=$true)]
[string]$Variable1,
[Parameter(Mandatory=$false)]
[string]$Variable2,
[Parameter(Mandatory=$false)]
[ValidateScript({[string]::IsNullOrWhiteSpace($Variable2)})]
[switch]$Choice
)
# function-body
}
}
Process {
Function-name -Variable1 "SomeString" -Choice
}
This cmdlet was called like below:
.\MyCmdlet.ps1 -variable1 "string1" -variable2 "string2"
It returns:
Cannot validate argument on parameter 'choice'. The "[string]::IsNullOrWhiteSpace($Variable2) " validation script for the argument with value "True" did not return a result of True.
It seems like the value of -Variable2 of cmdlet was implicitly passed to the function because of same variable name even without specically mentioning it during the function call inside the cmdlet.
Note: I need the variables to have same name so I can see their similar function in the future. And I use Begin, Process, End in cmdlet just so I can convert it into function and put in inside other scripts.
How can I deal with this?

By the time the ValidationScript runs, the local variable $Variable2 has not yet been created, so you get the value from $Variable2 in the parent scope.
Use $PSBoundParameters instead:
Function Function-Name {
Param (
[Parameter(Mandatory=$true)]
[string]$Variable1,
[Parameter(Mandatory=$false)]
[string]$Variable2,
[Parameter(Mandatory=$false)]
[ValidateScript({[string]::IsNullOrWhiteSpace($PSBoundParameters['Variable2'])})]
[switch]$Choice
)
}

If your intention is to create a script that could be both invoked directly (as a script) and dot-sourced (so you can call the function in it from other PowerShell scripts), you could do something like this:
# Invoke-Something.ps1
[CmdletBinding()]
Param(
[Parameter(Mandatory=$false)]
[string]$Variable1,
[Parameter(Mandatory=$false, ParameterSetName='bar')]
[string]$Variable2,
[Parameter(Mandatory=$true, ParameterSetName='foo')]
[switch]$Choice
)
function Invoke-Something {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[string]$Variable1,
[Parameter(Mandatory=$false, ParameterSetName='bar')]
[string]$Variable2,
[Parameter(Mandatory=$true, ParameterSetName='foo')]
[switch]$Choice
)
Write-Host 'invocation: function'
}
if ($MyInvocation.Line.Split()[0] -ne '.') {
Write-Host 'invocation: directly'
Invoke-Something #PSBoundParameters
} else {
Write-Host 'invocation: dot-source'
}
Using the above approach you can invoke the script directly:
PS> Invoke-Something.ps1 -Variable1 'some' -Variable2 'other'
invocation: directly
invocation: function
or dot-source it and invoke the function:
PS> . Invoke-Something.ps1
invocation: dot-source
PS> Invoke-Something -Variable1 'some' -Variable2 'other'
invocation: function
For this to work you must make all parameters of the script optional, though.
A simplified version of this would not define parameters on the script level and pass #args to the invoked function:
# Invoke-Something.ps1
function Invoke-Something {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[string]$Variable1,
[Parameter(Mandatory=$false, ParameterSetName='bar')]
[string]$Variable2,
[Parameter(Mandatory=$true, ParameterSetName='foo')]
[switch]$Choice
)
Write-Host 'invocation: function'
}
if ($MyInvocation.Line.Split()[0] -ne '.') {
Write-Host 'invocation: directly'
Invoke-Something #args
} else {
Write-Host 'invocation: dot-source'
}

Related

Auto complete values of the parameters powershell

I am trying to find the way to create values for parameters that can be auto completed or show all available options with tab, like the value "allsigned" in the cmdlet
Set-ExecutionPolicy -ExecutionPolicy AllSigned
¿Any ideas about how this is called in programation or how can I achieve this ?
yes its called validateset
Param
(
[parameter(Mandatory=$true)]
[ValidateSet("Low", "Average", "High")]
[String[]]
$Detail
)
advanced functions/cmdlets or just parameter validation, parameter sets, etc
snippet from the ISE:
function Verb-Noun
{
[CmdletBinding(DefaultParameterSetName='Parameter Set 1',
SupportsShouldProcess=$true,
PositionalBinding=$false,
HelpUri = 'http://www.microsoft.com/',
ConfirmImpact='Medium')]
[Alias()]
[OutputType([String])]
Param
(
# Param1 help description
[Parameter(Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
ValueFromRemainingArguments=$false,
Position=0,
ParameterSetName='Parameter Set 1')]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[ValidateCount(0,5)]
[ValidateSet("sun", "moon", "earth")]
[Alias("p1")]
$Param1,
# Param2 help description
[Parameter(ParameterSetName='Parameter Set 1')]
[AllowNull()]
[AllowEmptyCollection()]
[AllowEmptyString()]
[ValidateScript({$true})]
[ValidateRange(0,5)]
[int]
$Param2,
# Param3 help description
[Parameter(ParameterSetName='Another Parameter Set')]
[ValidatePattern("[a-z]*")]
[ValidateLength(0,15)]
[String]
$Param3
)
Begin
{
}
Process
{
if ($pscmdlet.ShouldProcess("Target", "Operation"))
{
}
}
End
{
}
}

Identify which parameter is passed when running a script

I have a simple code that accepts two parameters. The parameters are optional. Below is the code.
[CmdletBinding()]
Param(
[Parameter(Mandatory=$False)]
[string]$pA,
[Parameter(Mandatory=$False)]
[string]$pB
)
when running the script I want to know which parameter is passed. pA or pB.
$MyInvocation.BoundParameters
return a ps custom dictionary pair (key/value) with all passed parameter.
this is the content of a.ps1 file:
[CmdletBinding()]
Param(
[Parameter(Mandatory=$False)]
[string]$pA,
[Parameter(Mandatory=$False)]
[string]$pB
)
$MyInvocation.BoundParameters
running this script gives:
PS C:\ps> a -pA pAparam
Key Value
--- -----
pA pAparam
then you can check what key is present:
[bool]($MyInvocation.BoundParameters.Keys -match 'pa') # or -match 'pb' belong your needs
As you cas $Pa and $Pb you can test if they are empty:
You can test with this function :
function func
{
[CmdletBinding()]
Param([Parameter(Mandatory=$False)]
[string]$pA,
[Parameter(Mandatory=$False)]
[string]$pB
)
if ($pA -eq [string]::Empty -and $pA -eq [string]::Empty)
{
Write-Host "Both are empty"
}
elseif ($pA -ne [string]::Empty)
{
Write-Host "Pa is not empty"
}
elseif ($pB -ne [string]::Empty)
{
Write-Host "Pb is not empty"
}
}
Clear-Host
func
Remains the problem that func -Pa "" will give same results as func But if you just want to test the presence of a parameter you can use the switch attribute.
You can find more about PowerShell scripts and function parameters with these links:
about_Functions
about_Functions_Advanced
about_Functions_Advanced_Parameters

Display All Powershell Script Parameter Default Variables

Say I have a Powershell script TestParameters.ps1 like this, with two mandatory, named parameters and two optional parameters:
[CmdletBinding()]
Param (
[Parameter(Mandatory=$True)]
[string] $AFile = "C:\A\Path",
[Parameter(Mandatory=$True)]
[ValidateSet("A","B","C", "D")]
[string] $ALetter = "A",
[Parameter(Mandatory=$False)]
[ValidateNotNullOrEmpty()]
[string] $Optional1 = "Foo",
[Parameter(Mandatory=$False)]
[ValidateNotNullOrEmpty()]
[string] $Optional2 = "Bar"
)
echo "Hello World!"
$psboundparameters.keys | ForEach {
Write-Output "($_)=($($PSBoundParameters.$_))"
}
Say I call the script like this:
.\TestParameters.ps1 `
-AFile "C:\Another\Path" `
-ALetter "B"
which produces output:
Hello World!
(AFile)=(C:\Another\Path)
(ALetter)=(B)
Powershell set the variables $Optional1 and $Optional2 ... but how do I easily display them to the screen, like the way I use $PSBoundParameters?
I do not want to simply write the following every time I have a script:
Write-Host $AFile
Write-Host $ALetter
Write-Host $Optional1
Write-Host $Optional2
Notes:
$args only seems to include unbound parameters, not default
parameters
$MyInvocation seems to only include commands passed on the command lineEDIT: MyInvocation has member variable MyCommand.Parameters, which seems to have all the parameters, not just those passed on the command line...see the accepted answer, below.
Get-Variable seems to include the optional variables in the result list, but I do not know how to differentiate them from the other variables
The following seems to work on my box... probably not the best way to do it, but it seems to work in this case, at least...
[cmdletbinding()]
param([Parameter(Mandatory=$True)]
[string] $AFile = "C:\A\Path",
[Parameter(Mandatory=$True)]
[ValidateSet("A","B","C", "D")]
[string] $ALetter = "A",
[Parameter(Mandatory=$False)]
[ValidateNotNullOrEmpty()]
[string] $Optional1 = "Foo",
[Parameter(Mandatory=$False)]
[ValidateNotNullOrEmpty()]
[string] $Optional2 = "Bar"
)
echo "Hello World!"
($MyInvocation.MyCommand.Parameters ).Keys | %{
$val = (Get-Variable -Name $_ -EA SilentlyContinue).Value
if( $val.length -gt 0 ) {
"($($_)) = ($($val))"
}
}
Saved as allparams.ps1, and run it looks like:
.\allparams.ps1 -ALetter A -AFile "C:\Another\Path"
Hello World!
(AFile) = (C:\Another\Path)
(ALetter) = (A)
(Optional1) = (Foo)
(Optional2) = (Bar)
Using AST:
[CmdletBinding()]
Param (
[Parameter(Mandatory=$True)]
[string] $AFile = "C:\A\Path",
[Parameter(Mandatory=$True)]
[ValidateSet("A","B","C", "D")]
[string] $ALetter = "A",
[Parameter(Mandatory=$False)]
[ValidateNotNullOrEmpty()]
[string] $Optional1 = "Foo",
[Parameter(Mandatory=$False)]
[ValidateNotNullOrEmpty()]
[string] $Optional2 = "Bar"
)
echo "Hello World!"
$psboundparameters.keys | ForEach {
Write-Output "($_)=($($PSBoundParameters.$_))"
}
$ast = [System.Management.Automation.Language.Parser]::
ParseFile($MyInvocation.InvocationName,[ref]$null,[ref]$Null)
$ast.ParamBlock.Parameters | select Name,DefaultValue

Argument list and the pipeline

as an adjunct to this issue: How do I force declared parameters to require explicit naming? I am struggling with pipelines. Suppose I want the behaviour that this declares:
param(
$installdir,
$compilemode,
[Parameter(Position=0, ValueFromRemainingArguments=$true)] $files
)
namely, that I can call my script like this:
c:\> MyScript -installdir c:\ file-1.txt file-2.txt file-3.txt
but now I want to also be able to do it this way:
c:\> gi file-*.txt |MyScript -installdir c:\
I might think of adding a decoration to the parameter like this:
param(
$installdir,
$compilemode,
[Parameter(
Position=0,
ValueFromRemainingArguments=$true,
ValueFromPipeline=$true
)] $files
)
but what actually happens is I only get 1 argument into my parameter i.e. instead of getting an array with all the files that gi produced, I get only the first in the list.
A second way I attempted this was by using the $input variable (instead of using the ValueFromPipeline decorator), but then in trying to call the script I get the error:
The input object cannot be bound to any parameters for the command
either because the command does not take pipeline input or the input
and its properties do not match any of the parameters that take
pipeline input.
where can I go from here?
You could declare it without ValueFromRemainingArguments:
param(
[Parameter(
Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[Alias('PSPath')]
[string[]]
$files,
$installdir,
$compilemode
)
And then pass in multiple files as an array using the comma operator e.g.:
MyScript -installdir c:\ file-1.txt,file-2.txt,file-3.txt
Note: In order to accept input from commands like Get-Item and Get-ChildItem, use ValueFromPipelineByPropertyName and add a parameter alias "PSPath" that will find the PSPath property on the objects output by Get-Item/Get-ChildItem.
I have test this in ISE and it works fine:
function foo
{
param(
[Parameter(
Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[Alias('PSPath')]
[string[]]
$files,
$installdir,
$compilemode
)
process {
foreach ($file in $files) {
"File is $file, installdir: $installdir, compilemode: $compilemode"
}
}
}
foo a,b,c -installdir c:\temp -compilemode x64
ls $home -file | foo -installdir c:\bin -compilemode x86
FYI, this is a template I use all the time to create commands that take pipeline input or array input, as well as wildcard paths:
function Verb-PathLiteralPath
{
[CmdletBinding(DefaultParameterSetName="Path",
SupportsShouldProcess=$true)]
#[OutputType([output_type_here])] # Uncomment this line and specify the output type of this
# function to enable Intellisense for its output.
param(
[Parameter(Mandatory=$true,
Position=0,
ParameterSetName="Path",
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Path to one or more locations.")]
[ValidateNotNullOrEmpty()]
[SupportsWildcards()]
[string[]]
$Path,
[Alias("PSPath")]
[Parameter(Mandatory=$true,
Position=0,
ParameterSetName="LiteralPath",
ValueFromPipelineByPropertyName=$true,
HelpMessage="Literal path to one or more locations.")]
[ValidateNotNullOrEmpty()]
[string[]]
$LiteralPath
)
Begin
{
Set-StrictMode -Version Latest
}
Process
{
if ($psCmdlet.ParameterSetName -eq "Path")
{
if (!(Test-Path $Path)) {
$ex = new-object System.Management.Automation.ItemNotFoundException "Cannot find path '$Path' because it does not exist."
$category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
$errRecord = new-object System.Management.Automation.ErrorRecord $ex, "PathNotFound", $category, $Path
$psCmdlet.WriteError($errRecord)
}
# In the -Path (non-literal) case, resolve any wildcards in path
$resolvedPaths = $Path | Resolve-Path | Convert-Path
}
else
{
if (!(Test-Path $LiteralPath)) {
$ex = new-object System.Management.Automation.ItemNotFoundException "Cannot find path '$LiteralPath' because it does not exist."
$category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
$errRecord = new-object System.Management.Automation.ErrorRecord $ex, "PathNotFound", $category, $LiteralPath
$psCmdlet.WriteError($errRecord)
}
# Must be -LiteralPath
$resolvedPaths = $LiteralPath | Convert-Path
}
foreach ($rpath in $resolvedPaths)
{
if ($pscmdlet.ShouldProcess($rpath, "Operation"))
{
# .. process rpath
}
}
}
End
{
}
}

the variables can not be set by a function by dot souring

I want to implement a "Log" library in common.ps1 and then use dot souring to load it.
but it does not work as I expected I thought I can get the different value after calling SetLogConfiguratoion, but the value is not changed. then the "Log" function does not work becasue the log path is $null.
Do I miss-understand dot-sourcing?
write-host $g_nodeName ==> show $null
. 'C:\Test\Common.ps1'
write-host $g_nodeName ==> show "unkown"
SetLogConfiguratoion $sqlInstance (join-path $BackupShare 'RemvoeAgent.log')
write-host $g_nodeName ==> still "unkown"
Log 'ERROR' 'Test'
and Common.ps1 is as below
$g_logPath = $null
$g_nodeName = "Unknown"
function SetLogConfiguratoion
{
param
(
[Parameter(
Mandatory=$true,
HelpMessage='NodeName')]
[ValidateNotNullOrEmpty()]
[string]$NodeName,
[Parameter(
Mandatory=$true,
HelpMessage='LogPath')]
[ValidateNotNullOrEmpty()]
[string]$LogPath
)
if($LogPath.StartsWith('Microsoft.PowerShell.Core\FileSystem::'))
{
$g_logPath = $LogPath;
}
else
{
$g_logPath = 'Microsoft.PowerShell.Core\FileSystem::' + $LogPath;
}
$g_NodeName = $NodeName;
}
function Log
{
param
(
[Parameter(
Mandatory=$true,
HelpMessage='Log level')]
[ValidateNotNullOrEmpty()]
[ValidateSet(
'Error',
'Warning',
'Info',
'Verbose'
)]
[string]$level,
[Parameter(
Mandatory=$true,
HelpMessage='message')]
[ValidateNotNull()]
[string]$message
)
if($g_logPath -eq $null)
{
return
}
$time = Get-Date –format ‘yyyy/MM/dd HH:mm:ss’
$msg = "$time :: $level :: $nodeName :: $message"
Add-content $LogPath -value $message + '\n'
}
Dot sourcing the script will make it run in the local scope, and create the functions there, but you're still invoking the function in it's own scope. It's going to set $g_nodename in that scope. If you want all of that to run in the local scope, you need to dot source the script into the local scope to create the functions, then call the functions in the local scope (by preceding them with '. ' (note the space after the dot - that has to be there).
. SetLogConfiguratoion $sqlInstance (join-path $BackupShare 'RemvoeAgent.log')