Best practice for creating default values in Powershell - powershell

I am new to Powershell, but i am curious what is the best practice for creating default variable in Powershell. This is an example, which i am referencing. In case if you just want to initialize default variables, without intention to pass any parameters to function. Which is better way number 1 or 2 or none of them. :)
1.
function test
{
param ([int]$x = 5,[int]$y = 14)
$x * $y
}
2.
function test
{
[int]$x = 5
[int]$y = 14
$x * $y
}

It just depends on your use-case. If you truly never intend to change the variables, #2 is correct.
I think you just need to ask yourself what future use-cases might be. Would changing the values break your function? The ability to supply parameters is very useful, if not now, perhaps in the future.
Basically, if you're using the variables as FINAL, #2 is fine, but in all other cases I would say #1 is more correct.

If your intention is to take parameters to your function, use Param(). By default, undefined values will be $Null
Function Test
{
Param(
[Int]
$X = 5,
[Int]
$Y = 14
)
Return $X * $Y
}
Function Test2
{
$X=5; $Y=14
$X * $Y
}
> Test2
>> 70
> Test
>> 70
> Test 5 20
>> 100

Related

References to arrays/objects in PowerShell

I use the reference Variable to pass parameters to functions and to manipulate values across functions using the same base variable.
For my other script this worked fine, and maybe this is just a thought problem here, but why this isn't working?:
$Script:NestedLists = #("test", #("test_level_2"))
function AddToReference
{
param([ref]$RefVar)
$RefVar.Value += #("hi")
}
AddToReference -RefVar ([ref]($Script:NestedLists[1]))
$Script:NestedLists[1]
I thought the output of $Script:NestedLists[1] would be "test_level_2" and "hi" but it is just "test_level_2"
This little change made it work
$Script:NestedLists = #("test",#("test_level_2"))
function AddToReference
{
param ([ref]$RefVar) ($RefVar.value)[1] += , "hi"
}
addtoreference ([ref]$Script:NestedLists)
$Script:NestedLists[1]
Why moving the [1] to $refvar made it work, I have no idea, I wish I had a better understanding. Also, this get's really tricky because if you add a value to the first array in the $script it moves the [1] to [2] etc...
I would personally do something to keep each array separate, and at the end just combine them as needed...
$a = "first","array"
$b = "second","array"
$script:nested = $null
$script:nested += , $a
$script:nested += , $b
The "+= ," combines each array in a nested array. so [0] would equal $a and [1] would equal $b

How to create a loop to feed back the result as new param of the function? (PowerShell)

My current script is:
function F
{
param ([int]$OF)
$OF / 2
}
$OF = 100
if ((F $OF) -ge 2)
{
F (F $OF)
}
The result is 25 now. But what I want to do is to add a loop to this script. Within the loop, the result F (F $OF) will be fed back as the new $OF of the function. Then the function runs again. It will not stop feeding back the result until the result is -lt 2. The whole process is more like the optimization: keep reducing the value until it meets the objective. However, I am not sure how to create such a loop. Use FOR LOOP? Or DO UNTIL LOOP?
couple of ways to do this:
function F
{
param ([int]$OF)
do
{
$result = $of / 2
$of = $result
$result
}
while( $result -gt 2)
}
f -OF 200
-OR
function F2
{
param ([int]$OF)
$of / 2
}
#Declare the parameter value
$val = 100
do
{
$result = F2 -OF $val
$val = $result
$result
}
while($result -gt 2)
The original description in your question seems to point to a recursive method as a recursive function can run until a condition is met (in the same manner as a loop). In this case a simple while loop or do/while loop would probably work but this also provides an excellent opportunity to learn about recursion for later problems or assignments (such as flattening arrays or sorting/searching problems).
Simple Recursive Method
A note on the code: it is good coding practice to start learning naming standards. In this case, variables created within functions shouldn't have the same name as other functions (unless a $global is needed) and one letter functions are rarely a good idea - both of these can cause confusion and make code hard to parse later.
function CalculateF #changed from F
{
param ([int] $cur_of) #changed from $OF so no confusion with calling variable
$cur_of
if ($cur_of -gt 2)
{
CalculateF -cur_of ($cur_of/2) # calls itself with halved value
}
}
$OF = 100
if (CalculateF -cur_of $OF -ge 2) #changed these function calls to call the parameter directly
{
CalculateF -cur_of $OF
}
This function runs, prints the value of the current OF (cur_of), checks to see if the value of cur_of is greater than 2. Than CalculateF calls itself (that is simple recursion) with cur_of/2. Or if it is not it completes the function.
For Loop Version
As #Kiran already put a version with a do/while loop and function - I'll put the for loop version which does not require or make sense with a function at all. As a for loop can perform simple mathematical calculations on the iterator not just +/- 1, as such:
$val = 100
for ($i=$val; $i -ge 2; $i /= 2)
{
# Sets iterator to value and loops through (dividing by 2 each time)
[int] $i
}
Note: Tested above with powershell -version 2 ./scriptname.ps1 to ensure no versioning issues

PowerShell copy on write behavior depends on operator type

PowerShell uses copy-on-write semantic for inner scopes.
This means that if you'll change outer variable in the inner scope then inner scope would deal with it's own copy. Here is an example:
$list = #(1, 2)
Function Foo
{
"Foo: initial value - $list"
$list = $list + 3
"Foo: `$list: $list, `$global:list: $global:list"
}
The output would be:
Foo: initial value - 1 2
Foo: $list: 1 2 3, $global:list: 1 2
Ok, this behavior is by design. But lets change Foo function a bit:
Function Foo
{
"Foo: initial value - $list"
$list += 3
"Foo: `$list: $list, `$global:list: $global:list"
}
The output would be:
Foo: initial value - 1 2
Foo: $list: 3, $global:list: 1 2
Hm... I can assume that x += y operator is not exactly the same that x = x + y. But that is not exacted. Let's change the code once more:
Function Foo
{
New-Variable -Name z -Value 42
"Foo: initial value - $list"
$list += 3
"Foo: `$list: $list, `$global:list: $global:list"
}
And now the output would be:
Foo: initial value - 1 2
Foo: $list: 1 2 3, $global:list: 1 2
It seems like a bug. The only question is where exactly? I assume behavior of += in second case is incorrect. But maybe I missing something...
I tried these 3 test in PowerShell 2.0. All results are the same as in the
first test. I also tried a scalar value $list = 42 in order to see if the
issue is specific for arrays. In v2 all tests are the same (45, 45, 45), in v3
they get 45, 3, 45. So the issue is not just about arrays.
Then I took at look at breaking changes in v3.0, see WMF 3 Release Notes.docx
at WMF 3.0.
The document says:
Change
Read/Modify/Write operators no longer use dynamic scoping for the Read
operation. Also, compound equality operators (including +=, -=, *=, %=, ++,
--) do not use dynamic scoping. The variable is always in the current scope.
Sample
$x = 1
& { $x += 1; $x }
# Returns 2 in Windows PowerShell 2.0
# Returns 1 in Windows PowerShell 3.0
Error Message
No error. Results differ.
Workaround
Rewrite to use only the simple assignment operator:
$x = 1
& { $x = $x + 1; $x}
According to the document, in v3 the test 2 is correct and the test 3 shows a bug.
UPDATE: PowerShell 4.0 is the same as 3.0. I do not have v5 in order to test this.

Strongly typed references in PowerShell?

I know I can type parameters to functions in PowerShell using:
Param (
[int]$myIntParam
);
And I know I can pass by reference like this:
Param (
[ref]$myRefParam
);
Is it possible to insist that the reference is to a particular type? For example, is it possible to have it be of type "reference to integer"? Like in C, I would to "pointer to integer" as "int*"... Is there something analogous in PowerShell?
I tried googling around but couldn't find any info on this.
There is no syntax to specify a "reference-to-type", because ref is its own type in Powershell and not a modifier of other types. However, you can use a script validator to get the same result.
function f {
param(
[ValidateScript({$_.Value.GetType() -eq [Int32]})]
[ref] $i
)
$i.value += 1
"New value is $($i.value)"
}
> $x = 5
> f ([ref]$x)
New value is 6
> $x
6
> $y = 'hello'
> f ([ref]$y)
Exception: Cannot validate argument on parameter 'i'.

How to modify parent scope variable using Powershell

I'm fairly new to powershell, and I'm just not getting how to modify a variable in a parent scope:
$val = 0
function foo()
{
$val = 10
}
foo
write "The number is: $val"
When I run it I get:
The number is: 0
I would like it to be 10. But powershell is creating a new variable that hides the one in the parent scope.
I've tried these, with no success (as per the documentation):
$script:$val = 10
$global:$val = 10
$script:$val = 10
But these don't even 'compile' so to speak.
What am I missing?
You don't need to use the global scope. A variable with the same name could have been already exist in the shell console and you may update it instead. Use the script scope modifier. When using a scope modifier you don't include the $ sign in the variable name.
$script:val=10
The parent scope can actually be modified directly with Set-Variable -Scope 1 without the need for Script or Global scope usage. Example:
$val = 0
function foo {
Set-Variable -scope 1 -Name "Val" -Value "10"
}
foo
write "The number is: $val"
Returns:
The number is: 10
More information can be found in the Microsoft Docs article About Scopes. The critical excerpt from that doc:
Note: For the cmdlets that use the Scope parameter, you can also refer to scopes by number. The number describes the relative position of one scope to another. Scope 0 represents the current, or local, scope. Scope 1 indicates the immediate parent scope. Scope 2 indicates the parent of the parent scope, and so on. Numbered scopes are useful if you have created many recursive scopes.
Be aware that recursive functions require the scope to be adjusted accordingly:
$val = ,0
function foo {
$b = $val.Count
Set-Variable -Name 'val' -Value ($val + ,$b) -Scope $b
if ($b -lt 10) {
foo
}
}
Let me point out a third alternative, even though the answer has already been made. If you want to change a variable, don't be afraid to pass it by reference and work with it that way.
$val=1
function bar ($lcl)
{
write "In bar(), `$lcl.Value starts as $($lcl.Value)"
$lcl.Value += 9
write "In bar(), `$lcl.Value ends as $($lcl.Value)"
}
$val
bar([REF]$val)
$val
That returns:
1
In bar(), $lcl.Value starts as 1
In bar(), $lcl.Value ends as 10
10
If you want to use this then you could do something like this:
$global:val=0
function foo()
{
$global:val=10
}
foo
write "The number is: $val"
Perhaps the easiest way is to dot source the function:
$val = 0
function foo()
{
$val = 10
}
. foo
write "The number is: $val"
The difference here being that you call foo via . foo.
Dot sourcing runs the function as normal, but it runs inside the parent scope, so there is no child scope. This basically removes scoping. It's only an issue if you start setting or overwriting variables/definitions in the parent scope unintentionally. For small scripts this isn't usually the case, which makes dot sourcing really easy to work with.
If you only want a single variable, then you can use a return value, e.g.,
$val = 0
function foo()
{
return 10
}
$val = foo
write "The number is: $val"
(Or without the return, as it's not necessary in a function)
You can also return multiple values to set multiple variables in this manner, e.g., $a, $b, $c = foo if foo returned 1,2,3.
The above approaches are my preferred way to handle cross-scope variables.
As a last alternative, you can also place the write in the function itself, so it's writing the variable in the same scope as it's being defined.
Of course, you can also use the scope namespace solutions, Set-Variable by scope, or pass it via [ref] as demonstrated by others here. There are many solutions in PowerShell.