Print all parameters passed to a cmdlet - powershell

Is there an easy way to get all paramters of a PowerShell cmdlet as a hashtable? I want the ability to dump all paramters via Write-Verbose (for debugging issues). What I want to do:
Function verb-noun {
Param($p1, $p2, $p2)
$parameters = ... # Get all parameters as hash
$parameters.Keys | % { Write-Verbose "$_=$parameters.Item($_)" }
...
}

You are looking for the automatic $PSBoundParameters variable which is a dictionary containing all bound parameter (parameters that you pass to the function)

Related

Why are string passed as ArrayLists [duplicate]

Is there a resource on how to pass in a Object[] as a parameter within a PowerShell function?
Both of these functions are cmdlets and they are being exported correctly, but I cannot see the $Return object in my second function.
Is something like the following needed?
ParameterAttribute.ValueFromPipeline Property (System.Management.Automation)
# Within PowerShell code
$Return = My-Function -Param "value" # $Return is of type Object[]
$ModifiedReturn = My-SecondFunction -Input $Return
Where this is my function definition:
function My-SecondFunction
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)]
[Object[]]$Input
)
begin {}
process
{
Write-Host "test: $Input" # Does not return anything
}
end {}
}
$Input is the name of an automatic variable. Use a different name.
I recommend $InputObject as that is in common usage so it has a well-understood meaning, but usually that means you are accepting pipeline input as well.
Of course if there's a name that's more descriptive for this parameter, you should use that.
I have submitted this issue on the PowerShell GitHub project suggesting that Set-StrictMode be modified to check for automatic variable assignment.

How do I implement output variables in powershell?

I realize "output variables" is probably the wrong terminology here which is why my google searching has failed me. I'm guessing it revolves around explicitly setting variable scope, but I've tried reading the about_Scopes doc and it's just not clicking for me.
Essentially what I'm trying to do is implement the equivalent of the -SessionVariable argument from Invoke-RestMethod in my own module's function. In other words, I need a string parameter that turns into a variable in the caller's scope.
function MyTest
{
param([string]$outvar)
# caller scoped variable magic goes here
set-variable -Name $outvar -value "hello world"
# normal function output to pipeline here
Write-Output "my return value"
}
# calling the function initially outputs "my return value"
MyTest -outvar myvar
# referencing the variable outputs "hello world"
$myvar
For bonus points, how do things change (if at all) if I'm wrapping an existing function that has its own output variable and I want to effectively pass through the output variable name?
function MyWrapper
{
param([string]$SessionVariable)
Invoke-RestMethod -Uri "http://myhost" -SessionVariable $SessionVariable
# caller scoped variable magic goes here
set-variable -Name $SessionVariable -value $SessionVariable
}
# calling the wrapper outputs the normal results from Invoke-RestMethod
MyWrapper -SessionVariable myvar
# calling the variable outputs the WebRequestSession object from the inner Invoke-RestMethod call
$myvar
P.S. If it matters, I'm trying to keep the module compatible with Powershell v3+.
There's no need to attempt to implement this yourself, -OutVariable is a Common Parameter.
Add a CmdletBinding attribute to your param block to get these for free:
function MyTest {
[CmdletBinding()]
param()
return "hello world"
}
MyTest -OutVariable testvar
$testvar now holds the string value "hello world"
For your second example, where you need to set a value in the caller scope in addition to pipeline output, use the -Scope option with Set-Variable:
function MyTest {
[CmdletBinding()]
param([string]$AnotherVariable)
if([string]::IsNullOrWhiteSpace($AnotherVariable)){
Set-Variable -Name $AnotherVariable -Value 'more data' -Scope 1
}
return "hello world"
}
MyTest -AnotherVariable myvar
$myvar in the caller scope now contains the string value "more data". The 1 value passed to the Scope parameter means "1 level up"
#Mathias R. Jessen propose solution, which work well, when not defined in module. But not, if you put it in module. So, I provide another solution, which works, when put in module.
In advanced function (one which use [CmdletBinding()] attribute) you can use $PSCmdlet.SessionState to refer to SessionState of a caller. Thus, you can use $PSCmdlet.SessionState.PSVariable.Set('Name' ,'Value') to set variables in caller's SessionState.
Note: this solution will not work if not defined in module or if called from the same module, where it defined.
New-Module {
function MyTest1 {
[CmdletBinding()]
param([string]$outvar)
$PSCmdlet.SessionState.PSVariable.Set($outvar, 'Some value')
}
function MyTest2 {
[CmdletBinding()]
param([string]$outvar)
# -Scope 1 not work, because it executed from module SessionState,
# and thus have separate hierarchy of scopes
Set-Variable -Name $outvar -Value 'Some other value' -Scope 1
}
function MyTest3 {
[CmdletBinding()]
param([string]$outvar)
# -Scope 2 will refer to global scope, not the caller scope,
# so variable with same name in caller scope will hide global variable
Set-Variable -Name $outvar -Value 'Some other value' -Scope 2
}
} | Out-Null
& {
$global:a = 'Global value'
MyTest1 a
$a
$global:a
''
MyTest2 a
$a
$global:a
''
MyTest3 a
$a
$global:a
}

Default value of parameter is not used in function

I have a very basic PowerShell script:
Param(
[string]$MyWord
)
function myfunc([string] $MyWord) {
Write-Host "$MyWord"
}
myfunc #PSBoundParameters
This is how I execute it:
PS C:\> .\test.ps1 -MyWord 'hello'
hello
All fine. But I want to set a default value if -MyWord isn't specified.
I tried this:
Param(
[string]$MyWord='hi'
)
function myfunc([string] $MyWord) {
Write-Host "$MyWord"
}
myfunc #PSBoundParameters
But than the output of my script was just empty. It was printing nothing when I did not describe my parameter. (it only showed 'hello' if I specified the parameter).
I also tried:
Param(
[string]$MyWord
)
function myfunc([string] $MyWord) {
[string]$MyWord='hi'
Write-Host "$MyWord"
}
myfunc #PSBoundParameters
But than the output was of course always 'hi' and never 'hello'. Even when I executed the script with the parameter -MyWord 'hello'
Can someone explaining what I'm doing wrong?
When I'm not using the function it is working as expected:
Param(
[string]$MyWord='hi'
)
Write-Host $MyWord
Output:
PS C:\> .\test.ps1 -MyWord 'hallo'
hallo
PS C:\> .\test.ps1
hi
Automatic variable $PSBoundParameters, as the name suggests, contains only bound parameters, where bound means that an actual value was supplied by the caller.
Therefore, a parameter default value does not qualify as binding the associated parameter, so $MyWord with its default value of 'hi' does not become part of $PSBoundParameters.
Note: Arguably, a parameter with a default value should also be considered bound (it is bound by its default value, as opposed to by a caller-supplied value). Either way, it would be convenient to have an automatic variable that includes default values too, so as to enable simple and comprehensive passing through of arguments. A suggestion has been submitted to the PowerShell repository as GitHub issue #3285.
Workarounds
The following solutions assume that you want to pass the default value through, and don't want to simply duplicate the default value in function myfunc (as demonstrated in Ansgar Wiecher's helpful answer), because that creates a maintenance burden.
Regarding function syntax: The following two forms are equivalent (in this case), though you may prefer the latter for consistency and readability.[1]
function myfunc([string] $MyWord = 'hi') { ... }
parameter declaration inside (...) after the function name.
function myfunc { param([string] $MyWord = 'hi') ... }
parameter declaration inside a param(...) block inside the function body.
A simple fix would be to add the default value explicitly to $PSBoundParameters:
Param(
[string]$MyWord = 'hi'
)
function myfunc ([string] $MyWord){
Write-Host "$MyWord"
}
# Add the $MyWord default value to PSBoundParameters.
# If $MyWord was actually bound, this is effectively a no-op.
$PSBoundParameters.MyWord = $MyWord
myfunc #PSBoundParameters
To achieve what you want generically, you must use reflection (introspection):
param(
[alias('foop')]
[string]$MyWord = 'hi'
)
function myfunc ([string] $MyWord) {
Write-Host "$MyWord"
}
# Add all unbound parameters that have default values.
foreach ($paramName in $MyInvocation.MyCommand.Parameters.Keys) {
if (-not $PSBoundParameters.ContainsKey($paramName)) {
$defaultVal = Get-Variable -Scope Local $paramName -ValueOnly
# A default value is identified by either being non-$null or
# by being a [switch] parameter that defaults to $true (which is bad practice).
if (-not ($null -eq $defaultVal -or ($defaultVal -is [switch] -and -not $defaultVal))) {
$PSBoundParameters[$paramName] = $defaultVal
}
}
}
myfunc #PSBoundParameters
[1] The param(...) form is required if you need to use the [CmdletBinding()] attribute with non-default values, as well as in scripts (.ps1). See this answer.
A parameter is bound only if you actually pass it a value, meaning that a parameter's default value does not show up in $PSBoundParameters. If you want to pass script parameters into a function, you must replicate the script parameter set in the function parameter set:
Param(
[string]$MyWord = 'hi'
)
function myfunc([string]$MyWord = 'hi') {
Write-Host "$MyWord"
}
myfunc #PSBoundParameters
Maintaining something like this is easier if you define both parameter sets the same way, though, so I'd put the function parameter definition in a Param() block as well:
Param(
[string]$MyWord = 'hi'
)
function myfunc {
Param(
[string]$MyWord = 'hi'
)
Write-Host "$MyWord"
}
If you want to use "Param" enclose it in the function like this:
function myfunc {
Param(
[string]$MyWord='hi'
)
Write-Host "$MyWord"
}
Very simple way is,
function myfunc([string]$MyWord = "hi") {
Write-Output $MyWord
}

Pass object[] into a function in PowerShell

Is there a resource on how to pass in a Object[] as a parameter within a PowerShell function?
Both of these functions are cmdlets and they are being exported correctly, but I cannot see the $Return object in my second function.
Is something like the following needed?
ParameterAttribute.ValueFromPipeline Property (System.Management.Automation)
# Within PowerShell code
$Return = My-Function -Param "value" # $Return is of type Object[]
$ModifiedReturn = My-SecondFunction -Input $Return
Where this is my function definition:
function My-SecondFunction
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)]
[Object[]]$Input
)
begin {}
process
{
Write-Host "test: $Input" # Does not return anything
}
end {}
}
$Input is the name of an automatic variable. Use a different name.
I recommend $InputObject as that is in common usage so it has a well-understood meaning, but usually that means you are accepting pipeline input as well.
Of course if there's a name that's more descriptive for this parameter, you should use that.
I have submitted this issue on the PowerShell GitHub project suggesting that Set-StrictMode be modified to check for automatic variable assignment.

How to pass a switch parameter to another PowerShell script?

I have two PowerShell scripts, which have switch parameters:
compile-tool1.ps1:
[CmdletBinding()]
param(
[switch]$VHDL2008
)
Write-Host "VHDL-2008 is enabled: $VHDL2008"
compile.ps1:
[CmdletBinding()]
param(
[switch]$VHDL2008
)
if (-not $VHDL2008)
{ compile-tool1.ps1 }
else
{ compile-tool1.ps1 -VHDL2008 }
How can I pass a switch parameter to another PowerShell script, without writing big if..then..else or case statements?
I don't want to convert the parameter $VHDL2008 of compile-tool1.ps1 to type bool, because, both scripts are front-end scripts (used by users). The latter one is a high-level wrapper for multiple compile-tool*.ps1 scripts.
You can specify $true or $false on a switch using the colon-syntax:
compile-tool1.ps1 -VHDL2008:$true
compile-tool1.ps1 -VHDL2008:$false
So just pass the actual value:
compile-tool1.ps1 -VHDL2008:$VHDL2008
Try
compile-tool1.ps1 -VHDL2008:$VHDL2008.IsPresent
Assuming you were iterating on development, it is highly likely that at some point you are going to add other switches and parameters to your main script that are going to be passed down to the next called script. Using the previous responses, you would have to go find each call and rewrite the line each time you add a parameter. In such case, you can avoid the overhead by doing the following,
.\compile-tool1.ps1 $($PSBoundParameters.GetEnumerator() | ForEach-Object {"-$($_.Key) $($_.Value)"})
The automatic variable $PSBoundParameters is a hashtable containing the parameters explicitly passed to the script.
Please note that script.ps1 -SomeSwitch is equivalent to script.ps1 -SomeSwitch $true and script.ps1 is equivalent to script.ps1 -SomeSwitch $false. Hence, including the switch set to false is equivalent to not including it.
According to a power shell team's blog (link below,) since V2 there is a technique called splatting. Basically, you use the automatic variable #PsBoundParameters to forward all the parameters. Details about splatting and the difference between # and $ are explained in the Microsoft Docs article (link below.)
Example:
parent.ps1
#Begin of parent.ps1
param(
[Switch] $MySwitch
)
Import-Module .\child.psm1
Call-Child #psBoundParameters
#End of parent.ps1
child.psm1
# Begin of child.psm1
function Call-Child {
param(
[switch] $MySwitch
)
if ($MySwitch){
Write-Output "`$MySwitch was specified"
} else {
Write-Output "`$MySwitch is missing"
}
}
#End of child.psm1
Now we can call the parent script with or without the switch
PS V:\sof\splatting> .\parent.ps1
$MySwitch is missing
PS V:\sof\splatting> .\parent.ps1 -MySwitch
$MySwitch was specified
PS V:\sof\splatting>
Update
In my original answer, I sourced the children instead of importing it as a module. It appears sourcing another script into the original just makes the parent's variables visible to all children so this will also work:
# Begin of child.ps1
function Call-Child {
if ($MySwitch){
Write-Output "`$MySwitch was specified"
} else {
Write-Output "`$MySwitch is missing"
}
}
#End of child.ps1
with
#Begin of parent.ps1
param(
[Switch] $MySwitch
)
. .\child.ps1
Call-Child # Not even specifying #psBoundParameters
#End of parent.ps1
Maybe, this is not the best way to make a program, nevertheless, this is the way it works.
About Splatting(Microsoft Docs)
How and Why to Use Splatting (passing [switch] parameters)
Another solution. If you declare your parameter with a default value of $false:
[switch] $VHDL2008 = $false
Then the following (the -VHDL2008 option with no value) will set $VHDL2008 to $true:
compile-tool1.ps1 -VHDL2008
If instead you omit the -VHDL2008 option, then this forces $VHDL2008 to use the default $false value:
compile-tool1.ps1
These examples are useful when calling a Powershell script from a bat script, as it is tricky to pass a $true/$false bool from bat to Powershell, because the bat will try to convert the bool to a string, resulting in the error:
Cannot process argument transformation on parameter 'VHDL2008'.
Cannot convert value "System.String" to type "System.Management.Automation.SwitchParameter".
Boolean parameters accept only Boolean values and numbers, such as $True, $False, 1 or 0.