Use value of parameter in inner (global) function - powershell

In PowerShell, I'm trying to customise the prompt inside a function that creates a development shell. I do that by creating an inner function prompt, with global scropt.
function Enter-DevEnvironment {
Param(
[Parameter()] [ValidateSet('Debug', 'Release')] $flavor = 'Debug'
)
function global:prompt {
"[$flavor] $($executionContext.SessionState.Path.CurrentLocation)>"
}
}
The problem is that while the function Enter-DevEnvironment has a variable $flavor, this variable is not available for the prompt function.
I've workedaround this by creating a yet another global variable ($global:DevFlavor = $flavor), and using DevFlavor inside prompt, but it left me wonder, whether a cleaner solution is available. I.E. creating an inner function using values from the outer scope by value, and not refering to a variable that may or may not be defined.

This can be done without creating a global variable, by defining the prompt function using New-Item. This allows us to pass a ScriptBlock and use its method GetNewClosure() to bake the value of the -flavor parameter into the function.
function Enter-DevEnvironment {
Param(
[Parameter()] [ValidateSet('Debug', 'Release')] $flavor = 'Debug'
)
$null = New-Item Function:\global:prompt -Force -Value {
"[$flavor] $($executionContext.SessionState.Path.CurrentLocation)>"
}.GetNewClosure()
}

Related

Access a variable from parent scope

In Single Module Scenario: Running Set-Var returns 10.
# m.psm1
function Set-Var {
$MyVar = 10
Get-Var
}
function Get-Var {
$MyVar
}
In Nested Modules Scenario: Running Set-Var does not return any value.
# m1.psm1
function Get-Var {
$MyVar
}
# m.psm1
Import-Module .\m1.psm1
function Set-Var {
$MyVar = 10
Get-Var
}
How do I achieve the same effect as a single module with nested modules? Using $script:MyVar also does not work. However, I would like to keep the scope of the variable local to enable concurrent executions with different values.
Your code doesn't work because local variables are not inherited by functions in nested module context.
You can do this instead:
$null = New-Module {
function Get-Var {
[CmdletBinding()] param()
$PSCmdlet.SessionState.PSVariable.Get('MyVar').Value
}
}
The New-Module command creates an in-memory module, because this code only works when the caller is in a different module or script.
Use the CmdletBinding attribute to create an advanced function. This is a prerequisite to use the automatic $PSCmdlet variable, which we need in the next step.
Use its SessionState.PSVariable member to get or set a variable from the parent (module) scope.
This answer shows an example how to set a variable in the parent (module) scope.
See also: Is there any way for a powershell module to get at its caller's scope?

trying to pass variables from a powershell function to another

I am trying to create 2 variables in a function and then passing them into the second function.
the 2 variables just don't seem to be being passed in.
I Have tried returning the variables but doesn't seem to work either.
what am I missing?
enter code here
function get-PSnames
{
$tony = 'tiny'
$tlony = 'jommy'
}
function get-PSadded ([string] $name,[string]$name2)
{
$tony + $tlony
}
get-PSnames
get-PSadded "$tony, $tlony"
Variables in PowerShell are scoped, meaning they only exist for the duration of the function that they're created in.
What you'll want to do is create a function that returns the string values:
function Get-PSNames
{
'tiny'
'jommy'
}
You can then assign them to your own variables when you call the function:
$tony,$tlony = Get-PSNames
And pass them as arguments to your other function:
function Get-PSAdded([string]$Name, [string]$OtherName)
{
# Remember to use the same variable names you used when defining the function's parameters
$Name + $OtherName
}
# call the function
$tony,$tlony = Get-PSNames
$newName = Get-PSAdded -Name $tony -OtherName $tlony

How do I change the value of a variable using a function in powershell?

I want to check the count of the items in a variable and change the value of the variable depending upon the count. Further, I want to use this function to validate other variables as well. Below mentioned is the code.
$c=#()
function Status($s)
{
if($s.Count -eq "0"){
$s = "Fail"
}else{
$s="success"
}
}
Status $c
Here I expected the value of $c would be "Fail". But instead the value remains to be null.
Without fiddling around with scopes or sending variables by reference, why don't you simply have the function return 'Fail' or 'Success'?
BTW The .Count property is of type Int32, so you should not surround the value 0 with quotes, making it a string.
function Get-Status($s) {
if($s.Count -eq 0) { 'Fail' } else { 'Success' }
}
Now, if you want to overwrite the variable $c with the outcome of whatever the function returns, simply do:
$c = #()
$c = Get-Status $c # will reset variable $c to the string value of 'Fail'
P.S. I renamed the function so it conforms to the Verb-Noun naming convention in PowerShell
If you want to change multiple variables in one function you need references or scopes. Scopes will change the variable with the same name inside the function and globally. Calling a variable by reference is indifferent to the variable names outside the function.
Reference:
Working with references your variable in the function needs to be of type [ref] ( or System.Management.Automation.PSReference ). In that case the argument you use must be cast to [ref] and to this before calling the function enclose the var with brackets ([ref]$c). When using references, you can't just change the variable itself, but you need to work with its .value. The value of your reference represents your original variable. ([ref]$s.Value -eq $c)
Using that your code would look like this:
$c=#()
function Status([ref]$s) #Define $s as [ref]. only arguments of type [ref] are valid
{
if($s.value.Count -eq 0)
{
$s.Value = "Fail" #This will change the type of $c from array to string
}
else
{
$s.Value += "success" #This will recreate the array with an additional object (string)
}
}
Status ([ref]$c) #The variable must be cast to [ref] to be valid
$c
Scopes:
Normally a function is executed in a lower scope than the rest of the script. That means variables only exist in their scope and in lower scopes and changes in lower scopes won't reflect to higher scopes. However, you can directly address a variable in another scope using $<scope>:var ($script:s). The downside is you work with the variable itself. The name of the variable inside the function and outside must be the same. (reading the help for scopes is highly recommended)
Here is your code with scopes:
$s=#() #var needs to have the same name
function Status #No parameter here
{
if($script:s.Count -eq "0")
{
$script:s = "Fail" #This will change the type of $s from array to string
}
else
{
$script:s += "success" #This will recreate the array with an additional object (string)
}
}
Status
$s
For a more global function, here is a “get the value of the specified variable” function.
# PowerShell
function getVar($name){
return (Get-Variable -Name $name).Value
}
The only problem with this is that if you have two variables with different scopes, the function may return the wrong variable, therefore the wrong value.
Here is a function to set a variable. It suffers from the same cons as above though.
# PowerShell
function setVar($name, $value){
Set-Variable -Name $name -Value $value
}
You can use the -Scope $scope part to help if you need to.
Happy coding!

How to change array of PSCustomObject in Powershell

I can modify PSCustomObject property which is Array like:
$a.b.c += "new item"
Is it possible to do it with function? I mean to pass $a.b.c to function and to modify it. Seems that it's not trivial (if possible even): I tried [ref] but without success. Only one way which works for me is to return new value and to assign it, but this involve $a.b.c expression on both sides of function call which makes line long. I tried also:
function AddItem {
Param(
$Obj,
$Value,
[switch]$Uniq
)
$MutObj = [System.Collections.ArrayList]$Obj
$MutObj.Add($Value) > $null
}
but this also does not work, seems that $a.b.c += x is actually change property "c" of $a.b object and I have not $a.b in function scope. Is it achievable even with function and with modification in place in its body?
I think what you are asking is why the object doesn't change in the function.
Powershell handles Function variables as Values which means it creates another variable exactly like the one passed in and changes it in scope of the function. What you want to do is make this by Reference meaning it will mess with the same object passed into the function. This is done by the type [Ref] added to the value passed into the parameter. In this example the magic is handled here Additem -Object ([ref]$JSON) -Value "TEST"
Full script
function AddItem {
Param(
$Object,
$Value,
[switch]$Uniq
)
$Object.Value += $Value
}
$JSON = '[["aaa", "bbb"], ["ccc", "ddd"]]' | ConvertFrom-Json
Additem -Object ([ref]$JSON) -Value "TEST"
"0 : $($JSON[0])"
"0 : $($JSON[1])"
"0 : $($JSON[2])"

Default value of mandatory parameter in PowerShell

Is it possible to set a default value on a mandatory parameter in a function?
It works without having it set as an mandatory ...
Ie.
Function Get-Hello {
[CmdletBinding()]
Param([Parameter(Mandatory=$true)]
[String]$Text = $Script:Text
)
BEGIN {
}
PROCESS {
Write-Host "$Script:Text"
Write-Host "$Text"
}
END {
}
}
$Text = "hello!"
Get-Hello
Reason for asking this is because i have a function that has some required parameters and the function works perfect with calling it with the required parameters but i also want to make it possible for these variables to be defined in the scripts that use this function in a "better presentable & editable" way along with the function to be able to be run with defining the required parameters.
Hence if defined in the script scope it should take that as default else it should prompty for the value.
Thanks in Advance,
If you targeting to PowerShell V3+, then you can use $PSDefaultParameterValues preferences variable:
$PSDefaultParameterValues['Get-Hello:Text']={
if(Test-Path Variable::Script:Text){
# Checking that variable exists, so we does not return $null, or produce error in strict mode.
$Script:Text
}
}