3

For sake the of learning only.

Let's say I have a variable, in this example a textbox, how could I set many properties at once without typing the variable name over and over.

I've attempted some stuff my self, one of them work for some odd reason.

This example works. Break is used to shorten the loop.

 $Textbox | % {
    $_.Text = "Hello World"
    $_.Background = "Black"
    $_.Foreground = "Green"
    Break
 }

These two examples do not work and are here just to see what I've attempted.

This was with and without "$_."s and instead of a "." I've used "=" and "+=", none of which worked.

 $Textbox.@{
    Text = "Hello World"
    Background = "Black"
    Foreground = "Green"
 }

 $Textbox.({
    $_.Text = "Hello World"
    $_.Background = "Black"
    $_.Foreground = "Green"
 })

There may be a easier way to do, I haven't come across anything on google.

mklement0
  • 382,024
  • 64
  • 607
  • 775
Captor
  • 65
  • 3
  • 12

2 Answers2

7

What you're looking for is a language construct such as VB[Script]'s With statement, which allows you to set an implied context for "object-less" property references (such as .Text) inside a block.

There is no such construct in PowerShell.

Your 1st attempt is the closest emulation in PowerShell, albeit at the expense of performance (though that may not matter): in a pipeline, automatic variable $_ allows for a concise reference to the input object at hand.

Important: Do not use break inside a pipeline: it won't just exit the pipeline, it will exit any enclosing loop and, in the absence of one, the enclosing script. Use return instead.

That said, in the case at hand, with only a single input object, return is not needed.


As for your other attempts:

  • Syntax @{...} is only used for hashtable literals. Trying to use this syntax as a property name causes a syntax error.

  • Syntax (...) evaluates any single command / expression enclosed; {...} defines a script block.

    • A script block that isn't actually executed (which would require &), when used in a string context - such as a property name here, evaluates to its literal contents between { and }, i.e., a multiline string that clearly does not represent the name of an existing property of $TextBox, so the overall result is $null.

    • Note that a script run in the appropriate strict mode - with Set-StrictMode -Version 2 or higher - would have flagged the above attempt to access a non-existent property as an error.
      By contrast, an attempt to assign to a non-existent property always generates an error.


Note, however, that PowerShell offers convenient multi-property initialization in the context of constructing objects, which in PowerShell you can frequently also achieve with casts, via hashtable initializers.

# Cast a hashtable with property name/value pairs to
# [System.Windows.Forms.TextBox], which implicitly constructs
# an instance with the specified property values:
$TextBox = [System.Windows.Forms.TextBox] @{
  Text = "Hello World"
  Location = [Point]::new(10, 50)
}

The caveat is that the availability of this technique depends on whether the target type has a parameter-less constructor - see this answer for details.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Well that kinda stinks there isn't one. This answer is great btw. And thanks for the tip. How'd you know it doesn't exit the pipeline? Is there a way I can test that for myself? – Captor Feb 21 '17 at 23:10
  • @Captor: Thanks; put your 1st snippet in a script, place a command that produces output after the pipeline, then run the script: you'll see that the command after the pipeline never runs. – mklement0 Feb 21 '17 at 23:16
  • Ahh! I see. Get-Process indeed did not run. However with a return it did. This is all great information! Self taught or schooling? – Captor Feb 21 '17 at 23:24
  • @Captor: Self-/SO-taught. It's worth getting to know PowerShell's own help system, which includes _conceptual_ topics (those whose names start with `about_`) - start with `Get-Help -Detailed Get-Help`. That said, as great as the fundamentals of PowerShell's help system are, the actual topics are often lacking - so take the official help as a starting point. – mklement0 Feb 21 '17 at 23:30
  • 1
    2 years later and stumbled upon this. You just saved me from having to write one of the ugliest bits of code I would have ever had to do. Thank you! – Nick W. Jul 06 '20 at 00:46
1

In case you maintain dictionaries (hashtables) of properties for other purposes as well, you can write a simple function that applies the data to an object:

function Set-Properties(
    [parameter(ValueFromPipeline)] $inputObject,
    [hashtable] $properties,
    [switch] $passThru
) {
    process {
        Add-Member -InputObject $inputObject -NotePropertyMembers $properties -force
        if ([bool]$passThru) { $inputObject }
    }
}

Usage:

Set-Properties $Textbox @{
    Text = "Hello World"
    Background = "Black"
    Foreground = "Green"
}

$Textbox1, $Textbox2 | Set-Properties -properties @{
    Text = "Hello World"
    Background = "Black"
    Foreground = "Green"
} -passThru | ForEach {
    # do something
}
wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • That's convenient, but there are two caveats: (a) you're _forcing_ the creation of the specified properties, which means that you won't get an error if you accidentally assign to a property that previously didn't exist, and (b) using regular hashtables with unordered keys in combination with `Add-Member -Force` will typically change the enumeration order of the object's properties. – mklement0 Feb 22 '17 at 05:49
  • 1
    Yes, you wrote it better than I intended to. Anyway, this is a primitive simplified example. – wOxxOm Feb 22 '17 at 05:53