Jonathan.
Jonathan.

Reputation: 110

Powershell Calculated properties and Tee-object/Variables

I'm trying to write a few scripts using calculated properties (love that feature), and I seem to have an issue when I assign variables in it, for example:

ls | select @{N='What';E={get-acl | Tee-Object -Variable something}},@{N="ok";E={$something}}

it seems that the output I'm getting is: output

And I'm unsure if there's a way to save a variable so I can use it in a different calculated property or even just piped to the next command? ( I do know that you can use that variable in the same calculated property though.)

Thanks to anyone who helps.

Upvotes: 0

Views: 186

Answers (1)

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174485

If you need to construct many inter-related properties and Select-Object is too verbose or inefficient, the idiomatic approach is to pipe to ForEach-Object and construct the output object manually:

Get-ChildItem |ForEach-Object {
  $acl = $_ |Get-Acl

  # now we can just use this local variable directly
  $isOK = Test-WhateverYouNeedToTest $acl

  # construct new object
  [pscustomobject]@{
    FilePath  = $_.FullName
    ACL       = $acl
    RuleCount = $acl.Access.Count
    IsOK      = $isOK
  }
}

I'm unsure if there's a way to save a variable so I can use it in a different calculated property

You can't "cross-reference" the resolved value of one calculated property expression from another in the same call, if that's what you mean.

Property expressions are executed in their own local scope, which is why variable assignments also don't persist.

You could (but probably shouldn't) assign to a variable in a parent scope:

ls | select Name,@{N='ACL';E={ ($global:something = $_ |Get-Acl) }},@{N='RuleCount';E={$something.Access.Count}}

I would strongly recommend against it - keeping calculated properties free of side effects will make your code easier to read and understand.

[...] or even just piped to the next command

Again, you could, (but probably shouldn't) assign to a variable in a parent scope and use that with the downstream command:

ls | select @{N='ACL';E={ ($global:something = $_ |Get-Acl) }} | select ACL,@{N='RuleCount';E={$something.Access.Count}}

To explain why this is a bad idea, let's take this example instead:

function TimesTen {
  1..10 | select @{N='Base';E={$global:v10 = $_ * 10; $_}} | select Base,@{N='Tenfold';E={$v10}}
}

This might appear to work as intended:

PS ~> TimesTen

Base Tenfold
---- -------
   1      10
   2      20
   3      30
   4      40
   5      50
   6      60
   7      70
   8      80
   9      90
  10     100

But this only works as long as pipeline output from Select-Object isn't buffered:

PS ~> $PSDefaultParameterValues['Select-Object:OutBuffer'] = 4
PS ~> TimesTen

Base Tenfold
---- -------
   1      50
   2      50
   3      50
   4      50
   5      50
   6     100
   7     100
   8     100
   9     100
  10     100

Oops! Now you need to explicitly prevent that:

function TimesTen {
  1..10 | select @{N='Base';E={$global:v10 = $_ * 10; $_}} -OutBuffer 0 | select Base,@{N='Tenfold';E={$v10}}
}

With ForEach-Object, you avoid all of this.

Upvotes: 1

Related Questions