I'm trying to have a script with both executable code and a function, like the following:
function CopyFiles {
Param( ... )
...
}
// Parameter for the script
param ( ... )
// Executable code
However, I run into the following error: "The assignment expression is not valid. The input to an assignment operator must be an object that is able to accept assignments, such as a variable or a property"
When I list my function at the end of the file, it says that the function name is undefined. How do I call a powershell function from executable code within the same script?
The correct order is:
1.Script parameters
# Parameter for the script
param([string]$foo)
2.Function definitons
function CopyFiles {
Param([string]$bar)
...
}
3.Script code
# Executable code
CopyFiles $foo $bar
Why would you want it any other way?
Parameters go first always. I had a similar issue at one point in time with providing parameter input to a script. Your script should go:
param ( . . . )
# functions
# script body
For some reason, the PowerShell parsing engine doesn't appreciate the param keyword not being on the first line of a script, not counting comment lines. You can also do this:
param (
# params must be at the top of the file
)
You can also check to see if your parameters have been declared, or if they have the input you want, using Get-Variable. One other thing; if you want to cast data to a certain type, such as System.Boolean, I would do it AFTER the param block, and BEFORE functions. If you type-cast something to System.Boolean in the parameter declaration, then you'll have errors if people running your script don't submit the input argument in a Boolean value, which is much harder than using the .NET System.Convert static method to convert the value afterwards, and checking to see what it evaluated to.
Related
How would I go about retrieving the original expression used to invoke a Powershell function, from within the function itself? As in, the expression as it was typed/read from the script or command line.
I know that one can use $MyInvocation.Line to retrieve the first line of the invocation expression, however this won't correctly retrieve a multi-line expression.
( This answer only retrieves the first line, or the arguments, not the original expression)
It's really meant for the debugger, but I think you can use Get-PSCallStack for this:
Function Get-Something {
Param(
[Parameter(Mandatory = $true)]
[String]$Param1
)
(Get-PSCallStack)[1].Position.Text
}
Get-Something `
1
Multiple lines to call it. The function should return the exact line that was called.
Note: Inside the function I had to reference the second element returned from Get-PSCallStack this is because [0] would be the GetPSCallStack command itself. So, be forewarned, I can see this being finicky if it were nested down a bit.
Get-PSCallStack Documentation
I am a novice with PowerShell.
In Msys2 (or Lnux), I have defined a function npp
npp ()
{
${NPP_PATH} "$#"
}
such that if I call from the command prompt npp it launches Notepad++ (as defined in ${NPP_PATH}).
If I call npp "mydir/stage 1/a.txt" it opens that file for editing.
Generally speaking, it allows:
Any number of parameters.
Parameters containing spaces, if suitably escaped.
What would be the equivalent in PowerShell?
I guess in PS I should also go for a function to obtain a similar behavior.
So far, I could receive an undefined number of parameters, and use them in a foreach loop, see code below.
But I could not find the equivalent of the simple "$#" to pass all parameters as they are received.
Moreover, if I use quotes in one of the arguments, they are removed, so it will probably have problems with file paths including blanks.
function multi_params {
param(
[Parameter(
ValueFromRemainingArguments=$true
)][string[]]
$listArgs
)
$count = 0
foreach($listArg in $listArgs) {
'$listArgs[{0}]: {1}' -f $count, $listArg
$count++
}
}
Assuming that NPP_PATH is an environment variable, the equivalent PowerShell function is:
function npp {
& $env:NPP_PATH $args
}
If NPP_PATH is the name of a regular PowerShell variable, use & $NPP_PATH $args.
& is the call operator, which is needed for syntactic reasons whenever you want to invoke a command whose name/path is specified in quotes and/or via a variable.
In simple functions (as opposed to advanced functions) such as the above (use of neither [CmdletBinding()] nor [Parameter()] attributes), you can use the automatic $args variable to pass any arguments through to another command.
If the target command is not an external program, such as here, but a PowerShell command, use the form #args to ensure that all arguments - including those preceded by their parameter names - are properly passed through - see about_Splatting.
Note that the form #args works with external programs too, where it is generally equivalent to $args (the only difference is that only #args recognizes and removes --%, the stop-parsing token)
Note that passing arguments with embedded " chars. and empty arguments to external programs is still broken as of PowerShell v7.0 - see this answer.
Passing arguments through in simple vs. advanced functions (scripts):
In simple functions only, $args contains all arguments that did not bind to declared parameters, if any, on invocation.
If your simple function doesn't declare any parameters, as in the example above, $args contains all arguments passed on invocation.
If your simple function does declare parameters (typically via param(...)), $args contains only those arguments that didn't bind to declared parameters; in short: it collects any arguments your function did not declare parameters for.
Therefore, $args is a simple mechanism for collecting arguments not declared or known in advance, either to be used in the function itself - notably if declaring parameters isn't worth the effort - or to pass those arguments through to another command.
To pass arguments that comprise named arguments (e.g., -Path foo instead of just foo) through to another PowerShell command, splatting is needed, i.e. the form #args.
Note that while $args is technically a regular PowerShell array ([object[]]), it also has built-in magic to support passing named arguments through; a custom array can not be used for this, and the hash-table form of splatting is then required - see about_Splatting
In advanced functions, $args is not available, because advanced functions by definition only accept arguments for which parameters have been declared.
To accept extra, positional-only arguments, you must define a catch-all ValueFromRemainingArguments parameter, as shown in the question, which collects such arguments in an array-like[1] data structure by default.
To also support named pass-through arguments, you have two basic option:
If you know the set of potential pass-through parameters, declare them as part of your own function.
You can then use splatting with the $PSBoundParameters dictionary (hash table) - see below - to pass named arguments through, possibly after removing arguments meant for your function itself from the dictionary.
This technique is used when writing proxy (wrapper) functions for existing commands; the PowerShell SDK makes duplicating the pass-through parameters easier by allowing you to scaffold a proxy function based on an existing command - see this answer.
Otherwise, there is only a suboptimal solution where you emulate PowerShell's own parameter parsing to parse the positional arguments into parameter-name/value pairs - see this answer.
The automatic $PSBoundParameters variable is a dictionary that is available in both simple and advanced functions:
$PSBoundParameters applies only if your function declares parameters, and contains entries only for those among the declared parameters to which arguments were actually bound (passed) on invocation; the dictionary keys are the parameter names, albeit without the initial -.
Note that parameters bound by a default value are not included - see this GitHub issue for a discussion.
Again, note that in advanced functions you can only pass a given argument if a parameter was declared for it, so any argument passed in a given invocation is by definition reflected in $PSBoundParameters.
Because it is a dictionary (hash table), it can be used with hash-table based splatting - #PSBoundParameters - to pass named arguments through to other PowerShell commands and, since it is mutable, you have the option of adding or removing named arguments (such as the ones intended for your function itself).
[1] That type is [System.Collections.Generic.List[object]]; however, you can specify a collection type explicitly, such as [object[]] to get a regular PowerShell array.
Motivation
Reduce the maintenance of an Azure DevOps task that invokes a Powershell script with a lot of parameters ("a lot" could be 5).
The idea relies on the fact that Azure DevOps generates environment variables to reflect the build variables. So, I devised the following scheme:
Prefix all non secret Azure DevOps variables with MyBuild.
The task powershell script would call a function to check the script parameters against the MyBuild_ environment variables and would automatically assign the value of the MyBuild_xyz environment variable to the script parameter xyz if the latter has no value.
This way the task command line would only contain secret parameters (which are not reflected in the environment). Often, there are no secret parameters and so the command line remains empty. We find this scheme to reduce the maintenance of the tasks driven by a powershell script.
Example
param(
$DBUser,
[ValidateNotNullOrEmpty()]$DBPassword,
$DBServer,
$Configuration,
$Solutions,
$ClientDB = $env:Build_DefinitionName,
$RawBuildVersion = $env:Build_BuildNumber,
$BuildDefinition = $env:Build_DefinitionName,
$Changeset = $env:Build_SourceVersion,
$OutDir = $env:Build_BinariesDirectory,
$TempDir,
[Switch]$EnforceNoMetadataStoreChanges
)
$ErrorActionPreference = "Stop"
. $PSScriptRoot\AutomationBootstrap.ps1
$AutomationScripts = GetToolPackage DevOpsAutomation
. "$AutomationScripts\vNext\DefaultParameterValueBinding.ps1" $PSCommandPath -Required 'ClientDB' -Props #{
OutDir = #{ DefaultValue = [io.path]::GetFullPath("$PSScriptRoot\..\..\bin") }
TempDir = #{ DefaultValue = 'D:\_gctemp' }
DBUser = #{ DefaultValue = 'SomeUser' }
}
The described parameter binding logic is implemented in the script DefaultParameterValueBinding.ps1 which is published in a NuGet package. The code installs the package and thus gets access to the script.
In the example above, some parameters default to predefined Azure Devops variables, like $RawBuildVersion = $env:Build_BuildNumber. Some are left uninitialized, like $DBServer, which means it would default to $env:MyBuild_DBServer.
We can get away without the special function to do the binding, but then the script author would have to write something like this:
$DBServer = $env:MyBuild_DBServer,
$Configuration = $env:MyBuild_Configuration,
$Solutions = $env:MyBuild_Solutions,
I wanted to avoid this, because of the possibility of an accidental name mismatch.
The Problem
The approach does not work when I package the logic of DefaultParameterValueBinding.ps1 into a module function. This is because of the module scope isolation - I just cannot modify the parameters of the caller script.
Is it still possible to do? Is it possible to achieve my goal in a more elegant way? Remember, I want to reduce the cost associated with maintaining the task command line in Azure DevOps.
Right now I am inclined to retreat back to this scheme:
$xyz = $(Resolve-ParameterValue 'xyz' x y z ...)
Where Resolve-ParameterValue would first check $env:MyBuild_xyz and if not found select the first not null value out of x,y,z,...
But if the Resolve-ParameterValue method comes from a module, then the script must assume the module has already been installed, because it has no way to install it before the parameters are evaluated. Or has it?
EDIT 1
Notice the command line used to invoke the DefaultParameterValueBinding.ps1 script does not contain the caller script parameters! It does include $PSCommandPath, which is used to obtain the PSBoundParameters collection.
Yea, but it will require modifications to the calling script and the function. Pass the parameters by reference. Adam B. has a nice piece on passing parameters by reference in the following:
https://mcpmag.com/articles/2015/06/04/reference-variables-in-powershell.aspx
Net-net, the following is an example:
$age = 12;
function birthday {
param([ref]$age)
$age.value += 1
}
birthday -age ([ref]$age)
Write-Output $age
I've got an age of 12. I pass it into a function as a parameter. The function increments the value of $age by 1. You can do the same thing with a function in a module. You get my drift.
Is there a way to have some of the parameters mandatory based on some condition (for example, if one of the parameters is absent or false) in a PowerShell function?
My idea is to be able to call a function in two ways. A concrete example is a function that gets a list from SharePoint - I should be able to call it with relative URL of the list (one and only parameter) OR with a web URL and a list display name (two parameters, both mandatory, but only if list relative URL is not used).
As Christian indicated, this can be accomplished via ParameterSetNames. Take a look at this example:
function Get-MySPWeb {
[CmdletBinding(DefaultParameterSetName="set1")]
param (
[parameter(ParameterSetName="set1")] $RelativeUrl,
[parameter(ParameterSetName="set2")] $WebUrl,
[parameter(ParameterSetName="set2", Mandatory=$true)] $DisplayName
)
Write-Host ("Parameter set in action: " + $PSCmdlet.ParameterSetName)
Write-Host ("RelativeUrl: " + $RelativeUrl)
Write-Host ("WebUrl: " + $WebUrl)
Write-Host ("DisplayName: " + $DisplayName)
}
If you run it with -RelativeUrl Foo it will bind to "set1". If you call this function without any parameters it will also bind to "set1".
(Note - when no parameters are provided in PowerShell v3 (with Windows 8 consumer preview) it will bind to "set1", however it will error binding in PowerShell v2 unless you add [CmdletBinding(DefaultParameterSetName="set1")] to the parameter block. Thanks #x0n for the DefaultParameterSetName tip!)
If you try to run it with a parameter value from both sets you will get an error.
If you run it with -WebUrl Bar it will prompt you for a parameter value for DisplayName, because it's a mandatory parameter.
There is a much more powerful option, called dynamic parameters, which allows to dynamically add parameters depending on the value of other parameters or any other condition.
You must structure your script in a different way, declaring the regular parameters as usual, and including a DynamicParam block to create dynamic parameters, a Begin block to initialize variables using the dynamic parameters , and a Process block with the code run by the script, which can use regular parameters, and variables initialized in Begin. It looks like this:
param(
# Regular parameters here
)
DynamicParam {
# Create a parameter dictionary
$runtimeParams = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
# Populate it with parameters, with optional attributes
# For example a parameter with mandatory and pattern validation
$attribs = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$mandatoryAttrib = New-Object System.Management.Automation.ParameterAttribute
$mandatoryAttrib.Mandatory = $true
$attribs.Add($mandatory)
$patternAttrib = New-Object System.Management.Automation.ValidatePatternAttribute('your pattern here')
$attribs.Add($patternAttrib)
# Create the parameter itself with desired name and type and attribs
$param = New-Object System.Management.Automation.RuntimeDefinedParameter('ParameterName', String, $attribs)
# Add it to the dictionary
$runtimeParams.Add('ParameterName', $param)
# Return the dictionary
$ruintimeParams
}
Begin {
# If desired, move dynamic parameter values to variables
$ParameterName = $PSBoundParameters['ParameterName']
}
Process {
# Implement the script itself, which can use both regular an dynamic parameters
}
Of course, the interesting part is that you can add conditions on the DynamicParam section and the Begin section to create different parameters depending on anything, for example other parameter values. The dynamic parameters can have any name, type (string, int, bool, object...) an attributes (mandatory, position, validate set...), and they are created before the execution of the script so that you get parameter tab completion (IntelliSense) in any environment which supports it, like the PowerShell console, the PowerShell ISE or the Visual Studio Code editor.
A typical example would be to create a different set of dynamic parameters depending on the value of a regular parameter, by using a simple if in the DynamicParam section.
Google "PowerShell dynamic parameters" for extra information, like showing help for dynamic parameters. For example:
PowerShell Magazine Dynamic Parameters in PowerShell
You need to use parameters set naming.
You can assign an exclusive parameter to a different parameter set name.
I'm going back to the basics here but in Lua, you can define a table like so:
myTable = {}
myTable [1] = 12
Printing the table reference itself brings back a pointer to it. To access its elements you need to specify an index (i.e. exactly like you would an array)
print(myTable ) --prints pointer
print(myTable[1]) --prints 12
Now functions are a different story. You can define and print a function like so:
myFunc = function() local x = 14 end --Defined function
print(myFunc) --Printed pointer to function
Is there a way to access the body of a defined function. I am trying to put together a small code visualizer and would like to 'seed' a given function with special functions/variables to allow a visualizer to 'hook' itself into the code, I would need to be able to redefine the function either from a variable or a string.
There is no way to get access to body source code of given function in plain Lua. Source code is thrown away after compilation to byte-code.
Note BTW that function may be defined in run-time with loadstring-like facility.
Partial solutions are possible — depending on what you actually want to achieve.
You may get source code position from the debug library — if debug library is enabled and debug symbols are not stripped from the bytecode. After that you may load actual source file and extract code from there.
You may decorate functions you're interested in manually with required metadata. Note that functions in Lua are valid table keys, so you may create a function-to-metadata table. You would want to make this table weak-keyed, so it would not prevent functions from being collected by GC.
If you would need a solution for analyzing Lua code, take a look at Metalua.
Check out Lua Introspective Facilities in the debugging library.
The main introspective function in the
debug library is the debug.getinfo
function. Its first parameter may be a
function or a stack level. When you
call debug.getinfo(foo) for some
function foo, you get a table with
some data about that function. The
table may have the following fields:
The field you would want is func I think.
Using the debug library is your only bet. Using that, you can get either the string (if the function is defined in a chunk that was loaded with 'loadstring') or the name of the file in which the function was defined; together with the line-numbers at which the function definition starts and ends. See the documentation.
Here at my current job we have patched Lua so that it even gives you the column numbers for the start and end of the function, so you can get the function source using that. The patch is not very difficult to reproduce, but I don't think I'll be allowed to post it here :-(
You could accomplish this by creating an environment for each function (see setfenv) and using global (versus local) variables. Variables created in the function would then appear in the environment table after the function is executed.
env = {}
myFunc = function() x = 14 end
setfenv(myFunc, env)
myFunc()
print(myFunc) -- prints pointer
print(env.x) -- prints 14
Alternatively, you could make use of the Debug Library:
> myFunc = function() local x = 14 ; debug.debug() end
> myFunc()
> lua_debug> _, x = debug.getlocal(3, 1)
> lua_debug> print(x) -- prints 14
It would probably be more useful to you to retrieve the local variables with a hook function instead of explicitly entering debug mode (i.e. adding the debug.debug() call)
There is also a Debug Interface in the Lua C API.