powershell: script with variable args - powershell

I want to start a script1.ps1 out of an other script with arguments stored in a variable.
$para = "-Name name -GUI -desc ""this is the description"" -dryrun"
. .\script1.ps1 $para
The args I get in script1.ps1 looks like:
args[0]: -Name name -GUI -desc "this is the description" -dryrun
so this is not what I wanted to get.
Has anyone a idea how to solve this problem?
thx lepi
PS: It is not sure how many arguments the variable will contain and how they are going to be ranked.

You need to use splatting operator. Look at powershell team blog or here at stackoverflow.com.
Here is an example:
#'
param(
[string]$Name,
[string]$Street,
[string]$FavouriteColor
)
write-host name $name
write-host Street $Street
write-host FavouriteColor $FavouriteColor
'# | Set-Content splatting.ps1
# you may pass an array (parameters are bound by position)
$x = 'my name','Corner'
.\splatting.ps1 #x
# or hashtable, basically the same as .\splatting -favouritecolor blue -name 'my name'
$x = #{FavouriteColor='blue'
Name='my name'
}
.\splatting.ps1 #x
In your case you need to call it like this:
$para = #{Name='name'; GUI=$true; desc='this is the description'; dryrun=$true}
. .\script1.ps1 #para

Using Invoke-Expression is another aternative:
$para = '-Name name -GUI -desc "this is the description" -dryrun'
Invoke-Expression -Command ".\script1.ps1 $para"

Related

How to write out or trace specific commands in a PowerShell script?

In a PowerShell script that I'm creating, I want to output specific commands with the parameter values being passed. The output could go to a log file and/or the console output. The following will output what I want to the console but I have to duplicate the line of script of interest and at some point a subtle mistake will be made where the commands don't match. I've tried Set-PSDebug and Trace-Command and neither give the result I seek. I've thought about putting the line of script into a string, writing it out, and then calling Invoke-Expression but I'll give up autocompletion/intellisense.
Example with duplicated line for writing and execution:
Write-Output "New-AzureRmResourceGroup -Name $rgFullName -Location $location -Tag $tags -Force"
New-AzureRmResourceGroup -Name $rgFullName -Location $location -Tag $tags -Force
Output result with expanded variables. $tags didn't expand to actual hashtable values:
New-AzureRmResourceGroup -Name StorageAccounts -Location West US -Tag System.Collections.Hashtable -Force
What other options or commandlets can I use to achieve the tracing without writing duplicated code and maybe even expanding the hashtable?
There is no built-in feature I'm aware of that echoes a version of a command being executed with variables and expressions used in arguments expanded.
Even if there were, it would only work faithfully in simple cases, because not all objects have literal representations.
However, with limitations, you can roll your own solution, based on &, the call operator, and parameter splatting via a hashtable of argument values defined up front:
# Sample argument values.
$rgFullName = 'full name'
$location = 'loc'
$tags = #{ one = 1; two = 2; three = 3 }
# Define the command to execute:
# * as a string variable that contains the command name / path
# * as a hashtable that defines the arguments to pass via
# splatting (see below.)
$command = 'New-AzureRmResourceGroup'
$commandArgs = [ordered] #{
Name = $rgFullName
Location = $location
Tag = $tags
Force = $True
}
# Echo the command to be executed.
$command, $commandArgs
# Execute the command, using & and splatting (note the '#' instead of '$')
& $command #commandArgs
The above echoes the following (excluding any output from the actual execution):
New-AzureRmResourceGroup
Name Value
---- -----
Name full name
Location loc
Tag {two, three, one}
Force True
As you can see:
PowerShell's default output formatting results in a multi-line representation of the hashtable used for splatting.
The $tags entry, a hashtable itself, is unfortunately only represented by its keys - the values are missing.
However, you can customize the output programmatically to create a single-line representation that approximates the command with expanded arguments, including showing hashtables with their values, using helper function convertTo-PseudoCommandLine:
# Helper function that converts a command name and its arguments specified
# via a hashtable or array into a pseudo-command line string that
# *approximates* the command using literal values.
# Main use is for logging, to reflect commands with their expanded arguments.
function convertTo-PseudoCommandLine ($commandName, $commandArgs) {
# Helper script block that transforms a single parameter-name/value pair
# into part of a command line.
$sbToCmdLineArg = { param($paramName, $arg)
$argTransformed = ''; $sep = ' '
if ($arg -is [Collections.IDictionary]) { # hashtable
$argTransformed = '#{{{0}}}' -f ($(foreach ($key in $arg.Keys) { '{0}={1}' -f (& $sbToCmdLineArg '' $key), (& $sbToCmdLineArg '' $arg[$key]) }) -join ';')
} elseif ($arg -is [Collections.ICollection]) { # array / collection
$argTransformed = $(foreach ($el in $arg) { & $sbToCmdLineArg $el }) -join ','
}
elseif ($arg -is [bool]) { # assume it is a switch
$argTransformed = ('$False', '$True')[$arg]
$sep = ':' # passing an argument to a switch requires -switch:<val> format
} elseif ($arg -match '^[$#(]|\s|"') {
$argTransformed = "'{0}'" -f ($arg -replace "'", "''") # single-quote and escape embedded single quotes
} else {
$argTransformed = "$arg" # stringify as is - no quoting needed
}
if ($paramName) { # a parameter-argument pair
'-{0}{1}{2}' -f $paramName, $sep, $argTransformed
} else { # the command name or a hashtable key or value
$argTransformed
}
}
# Synthesize and output the pseudo-command line.
$cmdLine = (& $sbToCmdLineArg '' $commandName)
if ($commandArgs -is [Collections.IDictionary]) { # hashtable
$cmdLine += ' ' +
$(foreach ($param in $commandArgs.Keys) { & $sbToCmdLineArg $param $commandArgs[$param] }) -join ' '
} elseif ($commandArgs) { # array / other collection
$cmdLine += ' ' +
$(foreach ($arg in $commandArgs) { & $sbToCmdLineArg '' $arg }) -join ' '
}
# Output the command line.
# If the comamnd name ended up quoted, we must prepend '& '
if ($cmdLine[0] -eq "'") {
"& $cmdLine"
} else {
$cmdLine
}
}
With convertTo-PseudoCommandLine defined (before or above the code below), you can then use:
# Sample argument values.
$rgFullName = 'full name'
$location = 'loc'
$tags = #{ one = 1; two = 2; three = 3 }
# Define the command to execute:
# * as a string variable that contains the command name / path
# * as a hashtable that defines the arguments to pass via
# splatting (see below.)
$command = 'New-AzureRmResourceGroup'
$commandArgs = [ordered] #{
Name = $rgFullName
Location = $location
Tag = $tags
Force = $True
}
# Echo the command to be executed as a pseud-command line
# created by the helper function.
convertTo-PseudoCommandLine $command $commandArgs
# Execute the command, using & and splatting (note the '#' instead of '$')
& $command #commandArgs
This yields (excluding any output from the actual execution):
New-AzureRmResourceGroup -Name 'full name' -Location loc -Tag #{two=2;three=3;one=1} -Force:$True

Using a variable in a PowerShell script

I'm trying to put a small collection of simple scripts together for PowerShell to make life easier but am having some problems with variables in these scripts.
In a Linux environment I would use a variable in my scripts (usually $1, $2, etc....) like this to make things easier
sed -i 's/$1/$2/g' filename.conf
So that all I would need to do is this
./findreplace.sh old new filename.conf
In powershell, I want to achieve similar, specifically with this command:
Get-ADPrincipalGroupMembership account.name | select name
In this case, the $1 would be where 'user.name' is, so that I would be doing:
.\groups.ps1 user.name
Is there the facility for this?
Groups.ps1 should have the following content:
param( $user )
Get-ADPrincipalGroupMembership $user | Select Name
You can then invoke it as shown, or as .\Groups.ps1 -user user.name.
However, I'd probably try to do it as an "advanced command", and allow it to handle multiple names, and also names passed in via the pipeline:
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline=$true;ValueFromPipelineByPropertyName=$true)]
[Alias("Identity","sAMAccountName")
string[] $Users
)
PROCESS {
ForEach ($User in $Users) {
Get-ADPrincipalGroupMembership $User | Select Name
}
}
which would also allow you to do something like Get-ADUser | .\Groups.ps1
You can add for loop form example script above:
for ($i = 0; $i -lt $args.Length; $i++) { Set-Variable -Scope Script -Name ($i + 1) -Value $args[$i] }
Write-Host "1: $1"
Write-Host "2: $2"
But more easy is just to declare script parameter as:
param($1, $2)
Write-Host "1: $1"
Write-Host "2: $2"
By the way it's bad practice to declare variables as numbers.

Dynamically use variable in PowerShell

I have some predefined variables, eg:
$s1 = a
$s2 = b
...
$s[x] = x
I have a script that connects to a device via serial port, get some data, split the string and get a value I need.
The value that I have from the string I needed to use it to get a predefined variable.
In php I do it easily like this:
$var1 = a
$var2 = b
...
$var[x] = x
$i = something
echo ${"var_name$i"}
How can I do the same in PowerShell?
I tried
write-host $s$i
write-host $s{$i}
write-host ${s$i}
write-host ${"s$i"}
with no luck...all I get is either the value of $i or empty space
Use the Get-Variable cmdlet:
Write-Host $(Get-Variable "s$i" -ValueOnly)
You are looking for the Get-Variable cmdlet:
Get-Variable 'i'

How to get the parameter name in a powershell function

I would like to implement a function to output some debugging information.
I want the function, namely "Write-Foo", to work in the following way:
Write-Foo $SomeThing
# the output will be "SomeThing: (the value of Something)"
Is it possible to implement this function? I don't know how to get the name of the parameter of the function.
Thanks
This is close to what you want:
function Write-Foo($varName)
{
$varVal = $ExecutionContext.InvokeCommand.ExpandString("`$variable:$varName")
Write-Host "$($varName): $varVal"
}
Trying it out:
PS> $SomeThing = 'hello'
PS> Write-Foo SomeThing
SomeThing: hello
Another take:
function Write-Foo($varName)
{
$v = Get-Variable $varName
Write-Host ($v.Name + ": " + $v.Value)
}
Result:
PS C:\> $Something = "nothing"
PS C:\> Write-Foo SomeThing
Something: nothing
Another possibility (this assumes what you want to display is the name of the variable that was passed as an argument, not the name of the parameter it was passed to).
function Write-Foo($varName)
{
$var =
($MyInvocation.line -replace '\s*write-foo\s*\$','').trim()
'{0}: {1}' -f $var,$varName
}
$something = 'Hello'
write-foo $something
something: Hello
It's easier if you pass the name without the $
function Write-Foo($varName)
{
$var = Get-Variable $varName
'{0}: {1}' -f $var.Name,$var.Value
}
$something = 'Hello'
write-foo something
something: Hello
I'll also second #Ansgar Weicher's suggestion of writing that to the Debug stream so you don't pollute the pipeline with it.
You could define a function like this:
function Write-Foo($msg) {
Write-Host ("SomeThing: {0}" -f $msg)
}
or (if you want the output to be available for further processing) like this:
function Write-Foo($msg) {
"SomeThing: {0}" -f $msg
}
However, since you said you want to output debugging information, using the already existing Write-Debug cmdlet might be a better approach:
PS C:\> Write-Debug 'foo'
PS C:\> $DebugPreference
SilentlyContinue
PS C:\> $DebugPreference = 'Continue'
PS C:\> Write-Debug 'foo'
DEBUG: foo
You can access the function's parameters through $MyInvocation.BoundParameters, so this may be what you want:
function Write-Foo($varName) {
foreach ($key in $MyInvocation.BoundParameters.Keys) {
Write-Verbose ("{0}: {1}" -f ($key, $MyInvocation.BoundParameters[$key])) -Verbose
}
}
Write-Foo "Hello"
and the output looks like this:
VERBOSE: varName: Hello
If you want to be able to control when the debug information appears you can turn this into a cmdlet:
function Write-Foo {
[CmdletBinding()]
Param ($varName)
foreach ($key in $MyInvocation.BoundParameters.Keys) {
Write-Verbose ("{0}: {1}" -f ($key, $MyInvocation.BoundParameters[$key]))
}
}
Write-Foo "Hello"
Write-Foo "This will debug" -Verbose
and the first call produces no output while the second will show you:
VERBOSE: Verbose: True
VERBOSE: varName: This will debug
Naturally you can choose how exactly to output the debug information. Probably either Write-Debug (which usually prompts for each line of output) or Write-Verbose (which is usually suppressed) is appropriate here.

Getting all Named Parameters from Powershell including empty and set ones

I'm trying to find a way to get all parameter information from a powershell script. Ex script:
function test()
{
Param(
[string]$foo,
[string]$bar,
[string]$baz = "baz"
)
foreach ($key in $MyInvocation.BoundParameters.keys)
{
write-host "Parameter: $($key) -> $($MyInvocation.BoundParameters[$key])"
}
}
test -foo "foo!"
I'd like to get the values of $bar and $baz in a dynamic way without knowing the names of the parameters ahead of time.
I've looked through $MyInvocation properties and methods but I don't see anything besides parameters that are set/passed.
Update 1:
I'm close to getting it with:
function test()
{
Param(
[string]$foo,
[string]$bar,
[string]$baz = "baz"
)
foreach($var in (get-variable -scope private))
{
write-host "$($var.name) -> $($var.value)"
}
}
test -foo "foo!"
If i could filter out the script parameters vs the default parameters I would be good to go.
Update 2:
The final working solution looks like this:
function test {
param (
[string] $Bar = 'test'
, [string] $Baz
, [string] $Asdf
)
$ParameterList = (Get-Command -Name $MyInvocation.InvocationName).Parameters;
foreach ($key in $ParameterList.keys)
{
$var = Get-Variable -Name $key -ErrorAction SilentlyContinue;
if($var)
{
write-host "$($var.name) > $($var.value)"
}
}
}
test -asdf blah;
Check this solution out. This uses the CmdletBinding() attribute, which provides some additional metadata through the use of the $PSCmdlet built-in variable. You can:
Dynamically retrieve the command's name, using $PSCmdlet
Get a list of the parameter for the command, using Get-Command
Examine the value of each parameter, using the Get-Variable cmdlet
Code:
function test {
[CmdletBinding()]
param (
[string] $Bar = 'test'
, [string] $Baz
, [string] $Asdf
)
# Get the command name
$CommandName = $PSCmdlet.MyInvocation.InvocationName;
# Get the list of parameters for the command
$ParameterList = (Get-Command -Name $CommandName).Parameters;
# Grab each parameter value, using Get-Variable
foreach ($Parameter in $ParameterList) {
Get-Variable -Name $Parameter.Values.Name -ErrorAction SilentlyContinue;
#Get-Variable -Name $ParameterList;
}
}
test -asdf blah;
Output
The output from the command looks like this:
Name Value
---- -----
Bar test
Baz
Asdf blah
To read the value dynamically use the get-variable function / cmdlet
write-host (get-variable "foo")
To print out all of the parameters do the following
foreach ($key in $MyInvocation.BoundParameters.keys)
{
$value = (get-variable $key).Value
write-host "$key -> $value"
}
Hopefully, some may find this one-liner useful:
function test()
{
Param(
[string]$foo,
[string]$bar,
[string]$baz = "baz"
)
$MyInvocation.MyCommand.Parameters | Format-Table -AutoSize #{ Label = "Key"; Expression={$_.Key}; }, #{ Label = "Value"; Expression={(Get-Variable -Name $_.Key -EA SilentlyContinue).Value}; }
}
test -foo "foo!"
Result
Keys Value
---- -----
foo foo!
bar
baz baz
I found this most useful for PS4 (Windows 2012 R2) - it includes default values / optional parameters:
$cmdName = $MyInvocation.InvocationName
$paramList = (Get-Command -Name $cmdName).Parameters
foreach ( $key in $paramList.Keys ) {
$value = (Get-Variable $key -ErrorAction SilentlyContinue).Value
if ( $value -or $value -eq 0 ) {
Write-Host "$key -> $value"
}
}
For those of you who do not want to use cmdletbinding() here's a variation on the one liner I found above:
(Get-Command -Name $PSCommandPath).Parameters | Format-Table -AutoSize #{ Label = "Key"; Expression={$_.Key}; }, #{ Label = "Value"; Expression={(Get-Variable -Name $_.Key -EA SilentlyContinue).Value}; }
$PSCommandPath is always available
I played with the 2 solutions i liked in this thread, they both work.
however I needed to produce an error out on missing parameter for a build script
$cmdName = $MyInvocation.InvocationName
$paramList = (Get-Command -Name $cmdName).Parameters
foreach ( $key in $paramList.Keys ) {
$value = (Get-Variable $key -ErrorAction Stop)
#Write-Host $value.Value #remove comment for error checking
if ([string]::IsNullOrEmpty($value.Value)){
$(throw ("$key is a mandatory value please declare with -$key <Required value> " ))
}
}
What if I don't know what arguments or how many will be passed? For example, the function could be called with:
test -foo "value1" -bar "value2" -baz "value3"
and someone else might call it with:
test -variable1 "somevalue" -variable2 "somevalue2" -variable3 "somevalue3" -variable4 "value4"
I looked at $MyInvocation and the arguments come across as UnboundArguments, so theoretically I could "pair up" argument 0 with argument 1, 2 with 3, etc and error out if there's an odd number of UnboundArguments.
Stumbled upon this trying to do something similar and figured out my preferred option.
Function ParamTest {
Param (
$t1 = '1234',
$t2,
[switch]$3
)
$MyInvocation |Add-Member -Name:'Param' -MemberType:'NoteProperty' -Value:(
(Get-Variable -Scope:'Local' -Include:#($MyInvocation.MyCommand.Parameters.keys)|
ForEach-Object -begin:{$h=#{}} -process:{$h.add($_.Name,$_.Value)} -end:{$h}
))
$MyInvocation.Param
}
Result
Name Value
---- -----
t1 1234
3 False
t2
PSVersionTable
Name Value
---- -----
PSVersion 5.1.19041.1320
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.19041.1320
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
A streamlined solution that:
builds on your own approach now shown at the bottom of the question,
while also including an additional piece of information, namely whether each parameter was bound on invocation, i.e. whether an argument was passed or not, based on its presence in the automatic $PSBoundParameter variable, which is a dictionary containing the bound parameters and their values (arguments).
Note: $PSBoundParameters does not include parameters bound by a default value, only those parameters to which an argument was explicitly passed; for your use case, that is arguably desirable in order to distinguish between a default value and an explicit one, but in scenarios where arguments should be passed through it may be desirable to have default-value parameters included - see GitHub issue #3285.
function test {
param (
[string]$foo,
[string]$bar,
[string]$baz = "baz"
)
foreach ($paramName in $MyInvocation.MyCommand.Parameters.Keys) {
$bound = $PSBoundParameters.ContainsKey($paramName)
[pscustomobject] #{
ParameterName = $paramName
ParameterValue = if ($bound) { $PSBoundParameters[$paramName] }
else { Get-Variable -Scope Local -ErrorAction Ignore -ValueOnly $paramName }
Bound = $bound
}
}
}
test -foo "foo!"
The above yields the following:
ParameterName ParameterValue Bound
------------- -------------- -----
foo foo! True
bar False
baz baz False
Note: This solution also handles dynamic parameters correctly:
Such parameters are reflected in $MyInvocation.MyCommand.Parameters if they situationally apply, but - unlike regular static parameters - are never reflected in scope-local variables. If they apply and are also bound, they and their values are reflected in $PSBoundParameters.
Thus - given that an applicable dynamic parameter may not be bound - the Get-Variable call to look for scope-local variables representing static parameters:
Must explicitly be limited to the current scope with -Scope Local so as not to accidentally pick up unrelated variables of the same name from ancestral scopes for unbound dynamic parameters.
Must ignore errors from the potentially resulting failure to find a variable in the current scope, using -ErrorAction Ignore.
I wanted a compact string of parameter key/value pairs that I can write out when catching error. Use content of the other answers, I came up with this:
$parameters = (Get-Variable -Scope:'Local' -Include:#($MyInvocation.MyCommand.Parameters.keys) |
Select-Object Name, Value | ForEach-Object { "$($_.Name) : $($_.Value)" }) -join ' | '
Sample output:
ComputerName : SomePC | Directory : C:\Tools\LogExpert | UserName : Hans Wurst | YesOrNo : False
Thanks to all other posters with great & helpful answers above!
I'm cobbling together some of the above posts to try to meet my requirements.
I'd like to show Params in a Powershell-language-compatible format, so that one could easily see & display these params, and then also copy+pasta back into either a script as Param() Defaults (ParamsFormat1) or into a commandline/function call (CommandFormat2). I struggled with this for a while, so I hope this saves someone some time.
Collection of various Scripts from above:
# Note: there are some subtle differences between these versions below
# Specifically in the first line for *which* object you're checking for Parameters, and subsequently what type of Parameters you're looking at.
# PSCmdlet version REQUIRES [CmdletBinding()] on your function!!!
# $($(Get-Command -Name $($PSCmdlet.MyInvocation.InvocationName)).Parameters) | `
# %{ Get-Variable -Name $_.Values.Name -ErrorAction SilentlyContinue; }
# -Scope:'Local' FILTERS out any global params
# (Get-Variable -Scope:'Local' -Include:#($MyInvocation.MyCommand.Parameters.keys)) | ft
# PSCommandPath supposedly available/usable "in all situations" - so this may be the most useful of all of them, BUT it shows global params [Debug, ErrorAction, etc...]
# (Get-Command -Name $PSCommandPath).Parameters | `
# %{ Get-Variable -Name $_.Values.Name -ErrorAction SilentlyContinue; }
ParamsFormat1 / HYBRID VERSION: To output "Default Params" as you would see them in a function definition. Combines the -Scope:"Local" and "always available" versions to get BOTH param TYPES as well as Name, Value
Write-Host "Params("
# TBD Figure out how to expand #{} of System.Collections.Hashtable
# HYBRID VERSION: Combine the -Scope:"Local" and "always available" versions to get BOTH param TYPES as well as Name, Value
# If you remove LocalList Filter here, it will also show "Global" function properties like Debug, ErrorAction, etc...
$LocalList = $(Get-Variable -Scope:'Local' -Include:#($MyInvocation.MyCommand.Parameters.keys) | Select-Object -ExpandProperty Name) -join "|"
# VERSION: Wrapper script with DEFAULTS : normally DOES include Global vars [Debug, ErrorAction, etc]. but DOES preserve param order as-written.
((Get-Command -Name $PSCommandPath).Parameters | `
Select -ExpandProperty Values | `
Where-Object { $_.Name -Match $LocalList } | `
Format-Table -HideTableHeaders -AutoSize `
#{ Label="Type"; Expression={"[$($_.ParameterType )]"}; }, `
#{ Label="Name"; Expression={"`t`$$($_.Name)"}; }, `
#{ Label="Equals"; Expression={"="}; }, `
#{ Label="Value"; Expression={ If( $_.ParameterType -Match "String" ) { "`"$((Get-Variable -Name $_.Name -EA SilentlyContinue).Value)`"" } Else{ $((Get-Variable -Name $_.Name -EA SilentlyContinue).Value)}; }; }, `
#{ Label="RowEndComma"; Expression={ "," }; }
##{ Label="Value"; Expression={ $((Get-Variable -Name $_.Name -EA SilentlyContinue).Value) }; } # (OPTIONAL) Values only, no wrapping quotes
)
Write-Host ")";
CommandFormat2 / SIMPLER VERSION: Call with CommandLine Args (TYPES not needed). This filters out Global vars, does NOT preserve param ordering. (sorted alphabetically?)
# VERSION: Call with CommandLine Args (TYPES not available - TBD needs more work to display String, Array, and Hashmap/PsCustomObject ["", #() and {}] types better): filters out Global vars, does NOT preserve param ordering.
# If needed, cut+paste OPTIONAL lines down above Format-Table line.
# Where-Object { $_.Value } | ` # (Optional) remove any Null items.
# Sort-Object -Property Name | ` # (Optional) sort output
(Get-Variable -Scope:'Local' -Include:#($MyInvocation.MyCommand.Parameters.keys) | `
Format-Table -HideTableHeaders -AutoSize `
#{ Label="Name"; Expression={"`t-$($_.Name)"}; }, `
#{ Label="Equals"; Expression ={":"}; }, `
#{ Label="Value"; Expression={ If( $_.ParameterType -NotMatch "String" ) { $_.Value; } Else {"`"$($_.Value)`"";} }; Alignment="left"; }
)