You can’t use New-Item
to create new functions or aliases. It only works for files and directories.
But you can use Set-Item
to create new functions and aliases.
1 2 3 |
PS> Set-Item -Path function:Test-Function -Value { "hello world!" } PS> Test-Function hello world! |
How would I go about making a bunch of functions via a loop? The following code creates functions named Hello-<num>
which output <num>
when run.
1 |
foreach ($i in 1..10) { Set-Item -Path function:Hello-$i -Value { "$i" } } |
Does this work?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# get all functions with the word "Hello" in it PS> Get-ChildItem function: |?{ $_.Name -match "Hello" } CommandType Name ModuleName ----------- ---- ---------- Function Hello-1 Function Hello-10 Function Hello-2 Function Hello-3 Function Hello-4 Function Hello-5 Function Hello-6 Function Hello-7 Function Hello-8 Function Hello-9 # note to self: much easier to do the following instead PS> Get-Command -Verb Hello CommandType Name ModuleName ----------- ---- ---------- Function Hello-1 Function Hello-10 Function Hello-2 Function Hello-3 Function Hello-4 Function Hello-5 Function Hello-6 Function Hello-7 Function Hello-8 Function Hello-9 # does this work? PS> hello-2 10 PS> hello-5 10 # nope, all are returning the same output, let's check the definition PS> Get-Command hello-2 | select definition Definition ---------- "$i" # not right, let's check all the function defitions PS> Get-Command -Verb Hello | ft Name, Definition Name Definition ---- ---------- Hello-1 "$i" Hello-10 "$i" Hello-2 "$i" Hello-3 "$i" Hello-4 "$i" Hello-5 "$i" Hello-6 "$i" Hello-7 "$i" Hello-8 "$i" Hello-9 "$i" |
And there lies the problem. Instead of each definition containing the number as it’s supposed to be, they all contain the variable $i
. And since $i
is 10
when the foreach
loop terminated, all functions return the number 10.
I can test this by setting $i
to a different number:
1 2 3 |
PS> $i = 20 PS> hello-2 20 |
What I need is for $i
in the Set-Item
script-block to not be left as a variable, but to be “captured” (for lack of a better word) and set to whatever the value of the variable is at the time the script-block is created.
1 |
foreach ($i in 1..10) { Set-Item -Path function:Hello-$i -Value { "$i" } } |
If you examine the members of a script-block you’ll notice a method called GetNewClosure()
. That’s what I need to use here. This method “closes off” (like “closing a deal” or “let’s close somebody”, similar to “capturing somebody”) all variables in the script-block when it’s created.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
PS> foreach ($i in 1..10) { Set-Item -Path function:Hello-$i -Value { "$i" }.GetNewClosure() } # yaay, works! PS> hello-4 4 PS> hello-6 6 # the definitions still look like the previous definitions though PS> Get-Command -Verb Hello | ft Name, Definition Name Definition ---- ---------- Hello-1 "$i" Hello-10 "$i" Hello-2 "$i" Hello-3 "$i" Hello-4 "$i" Hello-5 "$i" Hello-6 "$i" Hello-7 "$i" Hello-8 "$i" Hello-9 "$i" |
Good to know!
Closures work with function parameters too. No side-effects as far as I know.
1 2 3 4 5 6 7 8 9 10 11 12 |
# creating functions, but with parameters this time PS> foreach ($i in 1..10) { Set-Item -Path function:Hello-$i -Value { Param($blah) "$i + $blah" }.GetNewClosure() } # testing whether they work. they do. PS> hello-2 2 + PS> hello-2 -blah 4 2 + 4 PS> hello-2 -blah 6 2 + 6 PS> hello-3 -blah 6 3 + 6 |