Display All Powershell Script Parameter Default Variables - powershell

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

Related

Powershell functions, parameters and arg

I write own powershell func for debug like:
function StartDebug {
param (
[PARAMETER(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
$FunctionName,
[PARAMETER(Mandatory = $false)]
$OtherArg
)
try {& $FunctionName $OtherArg} catch {...} finally {...}
and use it everyway, but i need more arg after $FunctionName. is it realistic to pass many arguments in this case bec use from 0 to 10 arg. do I have to list all the arguments that can be in the parameters of the function? like:
function StartDebug {
param (
[PARAMETER(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
$FunctionName,
[PARAMETER(Mandatory = $false)]
$OtherArg,
[PARAMETER(Mandatory = $false)]
$OtherArg1,
[PARAMETER(Mandatory = $false)]
$OtherArg2,
[PARAMETER(Mandatory = $false)]
$OtherArg3
)
try {& $FunctionName $OtherArg OtherArg1 OtherArg2 OtherArg3 } catch {...} finally {...}
but i dont use positional parameters in code and too many named parameters in code (~100)
Interested in any ideas about this. tnx!
The magic word is Splatting. You can provide an array or a hashtable containing your arguments to a function. The splatter is written with an #VariableName instead of the $:
function StartDebug {
param (
[PARAMETER(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
$FunctionName,
[PARAMETER(Mandatory = $false)]
$OtherArg
)
try {& $FunctionName #OtherArg # Watch out for the # in the OtherArg
} catch {$_} finally {}
}
$FunctionName = 'Get-ChildItem'
$Splatter = #{
Path = 'C:\'
Filter = 'Users'
Directory = $true
}
$Splatter2 = #('c:\')
StartDebug $FunctionName $Splatter
StartDebug $FunctionName $Splatter2
However if you want to use single items as $OtherArg you will have to provide them as single element array as can be seen with $Splatter2. Or extend your function to transform single arguments in arrays automatically, but thats up to you.
I think you better run it using scriptblock:
$result = Invoke-DeepDebug { Get-ChildItem -Path 'C:\' ; Get-Service -InformationAction Continue}
And in Invoke-DeepDebug you can work with $Command.AST as deep and detailed as you want.
Function Invoke-DeepDebug {
param(
[Parameter(Mandatory=$true, Position=0)]
[Scriptblock]$Command
)
Write-Host -f Cyan "Executing " -n
Write-Host -f Magenta $Command.Ast.Extent.Text -n
Write-Host -f Yellow " ... " -n
$result = $null
try {
$result = Invoke-Command $Command -ErrorAction Stop
Write-Host -f Green "OK!"
} catch {
Write-Host -f Red "Error"
Write-Host -f Red "`t$($_.Exception.Message)"
}
return $result
}

passing values to a function in a powershell script

I am curious if it's possible to pass arguments to a powershell module using object notation. Look at the following definition of a function:
function Get-Something
{
Param
(
[Parameter(Mandatory=$true, Position=0)]
[string] $Name,
[Parameter(Mandatory=$true, Position=1)]
[int] $Id
)
}
can I call this function in the following notation:
Get-Something(Name "Vance Refrigeration", Id "9")
instead of doing:
Get-Something -Name "Vance Refrigeration" -Id 9
As far as I know, the only other way than to type every variable with '-Name "Vance Refrigeration" -Id 9' is to do the following:
function Get-Something
{
Param
(
[Parameter(Mandatory=$true, Position=0)]
[string] $Name,
[Parameter(Mandatory=$true, Position=1)]
[int] $Id
)
Write-Host $name en $id
}
$object = #{Name = "Vance Refrigeration"; Id = "9"}
Get-Something #object
Or with the prettier styling for the object:
$object = #{
Name = "Vance Refrigeration"
Id = "9"
}
Get-Something #object

How to pipe multiple parameters with PowerShell ValueFromPipelineByPropertyName

I have the following simple PowerShell function:
function ValueFromPipelineTest
{
param
(
[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[string] $Param1,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[string] $Param2
)
Process
{
Write-Output "Param1: $Param1"
Write-Output "Param2: $Param2"
}
}
and if I pipe values into it like so:
'Hello' | ValueFromPipelineTest
#{ Param1 = 'Hello'; Param2 = 'there' } | ValueFromPipelineTest
The 1st line works as expected, producing the output:
Param1: Hello
Param2:
However the 2nd line produces the output:
Param1: System.Collections.Hashtable
Param2:
I'm actually looking to have the 2nd line produce the output:
Param1: Hello
Param2: there
I assume this is possible, but I'm just missing something. If there's a way to do it without using a hashtable I'm open to that. I'm just looking for an easy way to define an object with some properties and pipe it into my function.
Any thoughts on how to accomplish what I'm after? Thanks in advance!
You can do this by passing a custom object (rather than a hashtable) to your function. Example:
function Test-Pipeline {
param(
[Parameter(ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
[String] $Param1,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[String] $Param2
)
process {
"Param1: $Param1"
"Param2: $Param2"
}
}
$obj = [PSCustomObject] #{Param1 = "Hello";Param2 = "World"}
$obj | Test-Pipeline
# Output:
#
# Param1: Hello
# Param2: World
You could use Splatting but that isn't for pipeline.
$Params = #{
'Param1' = “Hello”
'Param2' = “There”
}
ValueFromPipelineTest #Params
Alternatively, try the below.
$Params = #{
Param1 = “Hello”
Param2 = “There”
}
function ValueFromPipelineTest
{
param
(
[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[hashtable]$Param
)
Process
{
Write-Output "Param1: $($Param["Param1"])"
Write-Output "Param2: $($Param["Param2"])"
}
}
$Params | ValueFromPipelineTest

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

# 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'
}

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