How to use .GetNewClosure() with the -Action value for an EngineEvent - powershell

& {
$action = & { $y = 100
return { write-host "Value: $y" }.getnewclosure()
}
[void] (register-engineevent -sourcei "foo" -action $action)
[void] (new-event -sourcei "foo")
}
The code above prints Value:, while I'd expect it to print Value: 100. Is something missing here?

The engine is calling GetNewClosure() on it, which is a better thing for it to do than not.
When I ran the above, I got an output of only Value:, but then I globally declared $y = 25 and ran [void](new-event -sourcei 'foo') again, the output was Value: 25. Then I changed its value again, $y = 38, and ran the event again: Value: 38.
If what you want is to bake in the value of $y at the time you create the action, one way to do that is to create the scriptblock in such a way that the value of $y becomes a literal or part of a literal:
$action = &{ $y = 100
return (invoke-expression "{write-host 'Value: $y'}")
}
This first parses the string to insert the value, so Invoke-Expression ends up doing the equivalent of this:
{write-host 'Value: 100'}
I'm not sure if there are any other ways to bake in the value other than composing the entire content of the scriptblock in a string and passing it through Invoke-Expression.
In response to your comment, further explanation of closures:
> $action100 = &{$y = 100; return {write-host "Value: $y"}.GetNewClosure()}
> &$action100
Value: 100
That's the result you expect, and that's what you get, because the scriptblock is still "closed" around the value of $y in the scope where GetNewClosure() was called.
> $y = 25
> &$action100
Value: 100
Still closed around the $y that is 100.
> $action25 = $action100.GetNewClosure()
> &$action25
Value: 25
This produces a new scriptblock that encloses variables in the current scope. It makes it re-evaluate what $y is in that scriptblock, and in this context, $y is now 25.
> $y = 38
> &$action100
Value: 100
> &$action25
Value: 25
> &($action100.GetNewClosure())
Value: 38
At this point, because $y is declared globally now, when you call New-Event it will use GetNewClosure() and re-evaluate $y to 38, and print Value: 38. You've been getting Value: because in the context where the engine events call GetNewClosure() the variable $y is not defined, so "Value: $y" becomes "Value: ".

Related

Missing property name after reference operator

I have a map, that is originally c++ code, file read, and parsed into a map. The original code was an enum, and didn't have values for all items. $fileContent:
enum{
Error_A = 110,
Error_B,
Error_C,
Error_D,
Error_E,
Error_F,
Error_G = 118,
...
};
I have read the file contents and put it in a map like this (works fine):
function Get-Contents_b{
[cmdletbinding()]
Param ([string]$fileContent)
#Error_AA = 20
# create an ordered hashtable to store the results
$errorMap = [ordered]#{}
# process the lines one-by-one
switch -Regex ($fileContent -split '\r?\n') {
'^[\s]*([\w]+)[\s=]*([-\d]*)' { # Error...=12345
$key,$value = ($matches[1,2])|ForEach-Object Trim
$errorMap[$key] = $value
}
}
...
Then I want to iterate over the map, and for the ones with enum values dependent on single digit increase from the previous, I want to assign the value of the previous value plus one. I'm trying to do that below, but getting the $previousKey, using $key-1, and then getting the value from that, is giving the error shown in the comment.
foreach ($key in $errorMap.$keys)
{
$previousKey = $errorMap.[($key-1)] #missing property name after the reference operator
Write-Host $errorMap.$previousKey
if(($errorMap.$key).Value = "")
{
$errorMap.$key.Value = $errorMap.$previousKey.Value + 1
}
}
Any ideas how to fix this or get the previous value and assign the next empty value the previous value plus one?
This is with powershell 5.1 and VSCode.
You're mistakenly mixing member (property) access via the . operator with indexed access via [...] - you must use one or the other.
However, what you want is positional access to your keys (which only works with an ordered hashtable):
foreach ($keyIndex in 0..($errorMap.Count-1))
{
if ('' -eq $errorMap[$keyIndex]) {
$previousValue = $errorMap[$keyIndex - 1]
Write-Host $previousValue
$errorMap[$keyIndex] = 1 + $previousValue
}
}
Why not create the values in your Hashtable straight away instead of filling the empties afterwards?
function Get-Contents_b{
[cmdletbinding()]
Param ([string]$fileContent)
# create an ordered hashtable to store the results
$errorMap = [ordered]#{}
$currentValue = 0
# process the lines one-by-one
switch -Regex ($fileContent -split '\r?\n') {
'^\s+(\w+)\s*[,=]'{
$key, $value = ($_ -split '[,=]', 2).Trim()
if ([string]::IsNullOrWhiteSpace($value)) { $value = $currentValue }
$errorMap[$key] = [int]$value
$currentValue = [int]$value + 1
}
}
# return the map
$errorMap
}
Get-Contents_b $enum
Output:
Name Value
---- -----
Error_A 110
Error_B 111
Error_C 112
Error_D 113
Error_E 114
Error_F 115
Error_G 118

Explain scoping for functions called from PowerShell closures

The following PowerShell code displays unexpected scoping behavior for functions called from closures. Can you explain whether this is "by design", or is a defect?
function runblock($block) {
$x = 4
& $block
}
function printx() {
" in printx: x=" + $x
}
"PSVersion $($PSVersionTable.PSVersion)"
$x = 1
$b = {"In block x=" + $x ; $x = 3 ; printx}
$x = 2
runblock $b
$x = 1
$b = {"In closure x=" + $x ; $x = 3 ; printx}.GetNewClosure()
$x = 2
runblock $b
Output from the above is
PSVersion 3.0
In block x=4
in printx: x=3
In closure x=1
in printx: x=4
Most of the output makes sense to me:
The script block outputs In block x=4 since its parent scope is the runblock function. The printx function outputs x=3 since its parent scope is the script block scope.
The closure outputs In closure x=1 since the value of $x was captured by GetNewClosure call. All as expected.
BUT:
The call to printx from the closure outputs in printx: x=4. So the scope that printx executes within is unaffected by the scope of the closure where $x = 3.
It seems strange to me that a function called from a normal script block does see the variables in the script block scope, but a function called from a closure does not see the variables in the closure.
Consider following code:
function Run {
param($ScriptBlock)
$a = 3
# Picture 4
& $ScriptBlock
}
function Print {
# Picture 6
"Print `$a=$a"
"Print `$b=$b"
}
$a = 1
$b = 1
# Picture 1
$SB = {
# Picture 5
"Closure `$a=$a"
"Closure `$b=$b"
Print
}.GetNewClosure()
# Picture 2
$a = 2
$b = 2
# Picture 3
Run $SB
It prints:
Closure $a=1
Closure $b=1
Print $a=3
Print $b=2
Picture 1: you start from global scope, where you define some variables.
Picture 2: GetNewClosure() create new module and copy variables to it. (red arrow show parent scope relationship)
Picture 3: you change value of variables. Module scope not affected.
Picture 4: function Run called. It create local variable $a. (blue arrow show call direction)
Picture 5: $SB script block called. Script block bound to module session state, so you transit to it.
Picture 6: function Print called. Function bound to global session state, so you return to it.

Where does a Perl subroutine get values missing from the actual parameters?

I came across the following Perl subroutine get_billable_pages while chasing a bug. It takes 12 arguments.
sub get_billable_pages {
my ($dbc,
$bill_pages, $page_count, $cover_page_count,
$domain_det_page, $bill_cover_page, $virtual_page_billing,
$job, $bsj, $xqn,
$direction, $attempt,
) = #_;
my $billable_pages = 0;
if ($virtual_page_billing) {
my #row;
### Below is testing on the existence of the 11th and 12th parameters ###
if ( length($direction) && length($attempt) ) {
$dbc->xdb_execute("
SELECT convert(int, value)
FROM job_attribute_detail_atmp_tbl
WHERE job = $job
AND billing_sub_job = $bsj
AND xqn = $xqn
AND direction = '$direction'
AND attempt = $attempt
AND attribute = 1
");
}
else {
$dbc->xdb_execute("
SELECT convert(int, value)
FROM job_attribute_detail_tbl
WHERE job = $job
AND billing_sub_job = $bsj
AND xqn = $xqn
AND attribute = 1
");
}
$cnt = 0;
...;
But is sometimes called with only 10 arguments
$tmp_det = get_billable_pages(
$dbc2,
$row[6], $row[8], $row[7],
$domain_det_page, $bill_cover_page, $virtual_page_billing,
$job1, $bsj1, $row[3],
);
The function does a check on the 11th and 12th arguments.
What are the 11th and 12th arguments when the function is passed only 10 arguments?
Is it a bug to call the function with only 10 arguments because the 11th and 12th arguments end up being random values?
I am thinking this may be the source of the bug because the 12th argument had a funky value when the program failed.
I did not see another definition of the function which takes only 10 arguments.
The values are copied out of the parameter array #_ to the list of scalar variables.
If the array is shorter than the list, then the excess variables are set to undef. If the array is longer than the list, then excess array elements are ignored.
Note that the original array #_ is unmodified by the assignment. No values are created or lost, so it remains the definitive source of the actual parameters passed when the subroutine is called.
ikegami suggested that I should provide some Perl code to demonstrate the assignment of arrays to lists of scalars. Here is that Perl code, based mostly on his edit
use strict;
use warnings;
use Data::Dumper;
my $x = 44; # Make sure that we
my $y = 55; # know if they change
my #params = (8); # Make a dummy parameter array with only one value
($x, $y) = #params; # Copy as if this is were a subroutine
print Dumper $x, $y; # Let's see our parameters
print Dumper \#params; # And how the parameter array looks
output
$VAR1 = 8;
$VAR2 = undef;
$VAR1 = [ 8 ];
So both $x and $y are modified, but if there are insufficient values in the array then undef is used instead. It is as if the source array was extended indefinitely with undef elements.
Now let's look at the logic of the Perl code. undef evaluates as false for the purposes of conditional tests, but you apply the length operator like this
if ( length($direction) && length($attempt) ) { ... }
If you have use warnings in place as you should, Perl would normally produce a Use of uninitialized value warning. However length is unusual in that, if you ask for the length of an undef value (and you are running version 12 or later of Perl 5) it will just return undef instead of warning you.
Regarding "I did not see another definition of the function which takes only 10 arguments", Perl doesn't have function templates like C++ and Java - it is up to the code in the subroutine to look at what it has been passed and behave accordingly.
No, it's not a bug. The remaining arguments are "undef" and you can check for this situation
sub foo {
my ($x, $y) = #_;
print " x is undef\n" unless defined $x;
print " y is undef\n" unless defined $y;
}
foo(1);
prints
y is undef

Meaning of the '+=' operator

Could someone help me to understand what the '+=' operator means in a particular situation. The script says:
$receipts{$weather} += $receipt;
$days{$weather}++;
Assuming $foo += $bar, the += operator does the following:
$foo = $foo + $bar;
That is, increments $foo by $bar. Assuming $foo++, the ++ operator does the following:
$foo = $foo + 1;
That is, increments the variable by one.
With all this said, these operators also have some hidden perl magic. For example, the += and ++ operator does not give an uninitialized warning where the corresponding statement would:
# $foo is undefined
$foo += 10; # no warning
$foo++; # no warning
$foo = $foo + 10 # Use of uninitialized value $foo in addition
The ++ operator also works on strings
my $foo = 'a';
$foo++;
print $foo; # prints 'b'
The ++ operator comes in two flavours, post increment and pre increment. The return value of the expression is either calculated before or after the incrementation:
$foo = 1;
print ++$foo; # prints 2
print $foo++; # prints 2, but $foo is now 3
It is adding the value of $receipt to the value of $receipts{$weather} and storing the result back into $receipts{$weather}. It is the equivalent of:
$receipts{$weather} = $receipts{$weather} + $receipt
However, it may be implemented more efficiently in some cases.
See perldoc perlop:
"=" is the ordinary assignment operator.
Assignment operators work as in C. That is,
$a += 2;
is equivalent to
$a = $a + 2;
Example:
this example : int i = 2; i=i+4; and this example int i = 2; i+=4 are the same;

Why are variable assignments within a Trap block not visible outside it?

Why are the variable assignments that I make inside the Trap block not visible outside it?
$integer = 0;
$string = [String]::Empty;
$stringBuilder = new-object 'System.Text.StringBuilder';
trap
{
$integer = 1;
$string = '1';
$stringBuilder.Append('1');
write-host "Integer Variable Inside: " $integer;
write-host "String Variable Inside: " $string;
write-host "StringBuilder Variable Inside: " $stringBuilder;
continue;
}
$dummy = 1/$zero;
write-host "Integer Variable Outside: " $integer;
write-host "String Variable Outside: " $string;
write-host "StringBuilder Variable Outside: " $stringBuilder;
I would have expected the results from within and outside the Trap block to be identical but these are the results that I am seeing.
Integer Variable Inside: 1
String Variable Inside: 1
StringBuilder Variable Inside: 1
Integer Variable Outside: 0
String Variable Outside:
StringBuilder Variable Outside: 1
Notice that it is only the StringBuilder that retains its value.
I am guessing that this has something to do with the difference between value and reference types but can't quite pin it down.
With info that slipsec provided above and through some further experimentation, I now understand what is happening here.
Joel explains how the Trap scope works as follows.
Even though in our error handler we
were able to access the value of
$Result and see that it was True … and
even though we set it to $False, and
printed it out so you could see it was
set … the function still returns True,
because the trap scope doesn’t modify
the external scope unless you
explicitly set the scope of a
variable. NOTE: If you had used
$script:result instead of $result (in
every instance where $result appears
in that script), you would get the
output which the string/comments led
you to expect.
So variables from outside the Trap scope can be read but not set because they are copies of the originals (thanks Jason). This is the reason why the Integer variable did not retain its value. The StringBuilder however, is a reference object and the variable is only a pointer to that object. The code within the Trap scope was able to read the reference that the variable was set to and modify the object to which it was pointing - the variable itself required no change.
Note that Joel's tip about specifying the scope of the variable allowed me to set the value of the Integer variable from within the Trap scope.
$script:integer = 0;
$string = [String]::Empty;
$stringBuilder = new-object 'System.Text.StringBuilder';
trap
{
$script:integer = 1;
$string = '1';
$stringBuilder.Append('1');
write-host "Integer Variable Inside: " $script:integer;
write-host "String Variable Inside: " $string;
write-host "StringBuilder Variable Inside: " $stringBuilder;
continue;
}
$dummy = 1/$zero;
write-host "Integer Variable Outside: " $script:integer;
write-host "String Variable Outside: " $string;
write-host "StringBuilder Variable Outside: " $stringBuilder;
...and these are the results.
Integer Variable Inside: 1
String Variable Inside: 1
StringBuilder Variable Inside: 1
Integer Variable Outside: 1
String Variable Outside:
StringBuilder Variable Outside: 1
Note that the string variable does not retain its value because although it is a reference type, it is also immutable.
Rather than re-write Jakul's excelent post on the subject, I'll just link it:
http://huddledmasses.org/trap-exception-in-powershell/
Gobs of information on how powershell deals with error handling with plenty of detail.