You need to hear $this
On this page
If you’ve written any PowerShell at all, you know $_. It’s everywhere. $this is its quieter cousin, and it only shows up once you start doing slightly more interesting things: writing classes, bolting extra properties onto objects, or throwing together a quick GUI.
The short version is that $this means “me”. When code runs on an object, $this is that object.
Where $this exists
It’s an automatic variable, so you never set it yourself. PowerShell fills it in, but only in a handful of places:
| Where the code runs | What $this points at |
|---|---|
| A class method or constructor | The current instance of the class |
A ScriptProperty or ScriptMethod (via Add-Member or Update-TypeData) | The object the member was called on |
A .NET event handler, such as a WinForms Add_Click({}) | The control that raised the event |
| Anywhere else | Nothing. It’s $null |
That last row matters. Use $this in the wrong place and you don’t get an error, you just get nothing, which is exactly what makes it easy to get wrong.
It also helps to see it next to the variables it gets mixed up with:
$_(or$PSItem) is the current item in the pipeline$thisis the object that owns the code that’s running$argsis whatever was passed in
Classes
This is where most people first meet it. Inside a class you have to use $this to reach the instance’s own properties and methods. There’s no shortcut.
class Server {
[string] $Name
[string] $Role
[bool] $Patched
Server([string]$Name, [string]$Role) {
$this.Name = $Name
$this.Role = $Role
$this.Patched = $false
}
[void] MarkPatched() {
$this.Patched = $true
}
[string] Summary() {
return "$($this.Name) ($($this.Role)) patched: $($this.Patched)"
}
}
$srv = [Server]::new('DC01', 'Domain Controller')
$srv.MarkPatched()
$srv.Summary()
# DC01 (Domain Controller) patched: TrueOne more class rule: static methods don’t belong to an instance, so there’s no $this in them. Use [Server]::Something instead.
Adding a calculated property to an object
Add-Member lets you attach a ScriptProperty to an object you already have. Inside the script block, $this is the object. The value is worked out every time you read it, so it stays current.
$user = [pscustomobject]@{
GivenName = 'Alex'
Surname = 'Baker'
}
$user | Add-Member -MemberType ScriptProperty -Name DisplayName -Value {
"$($this.GivenName) $($this.Surname)"
}
$user.DisplayName # Alex Baker
$user.Surname = 'Smith'
$user.DisplayName # Alex SmithAdding a method to an object
Same idea, but a ScriptMethod does something rather than returning a value.
$vm = [pscustomobject]@{ Name = 'app-vm01'; State = 'Stopped' }
$vm | Add-Member -MemberType ScriptMethod -Name Start -Value {
$this.State = 'Running'
"Started $($this.Name)"
}
$vm.Start() # Started app-vm01
$vm.State # RunningA property with a getter and a setter
A ScriptProperty can take a second script block to act as a setter. The getter uses $this as normal. The setter also gets $this, but the new value arrives in $args[0], not $value or $_.
$cfg = [pscustomobject]@{ _Env = 'dev' }
$cfg | Add-Member -MemberType ScriptProperty -Name Environment `
-Value { $this._Env.ToUpper() } `
-SecondValue {
if ($args[0] -notin 'dev', 'test', 'prod') {
throw "Invalid environment: $($args[0])"
}
$this._Env = $args[0]
}
$cfg.Environment # DEV
$cfg.Environment = 'prod'
$cfg.Environment # PROD
$cfg.Environment = 'uat' # throwsThat’s a cheap way to get validation on an object without writing a full class.
Extending every object of a type
Add-Member works on one object. Update-TypeData adds the member to every object of a .NET type for the rest of your session. I find this handy for things I always want to see, like file sizes in MB.
Update-TypeData -TypeName System.IO.FileInfo -MemberType ScriptProperty `
-MemberName SizeMB -Value { [math]::Round($this.Length / 1MB, 2) } -Force
Get-ChildItem $env:TEMP -File | Select-Object Name, SizeMBGUI event handlers
If you’ve built a WinForms tool, you’ve probably used $this without thinking about it. Inside an event script block, $this is the control that fired the event and $_ is the event arguments. That means one handler can serve lots of buttons.
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object System.Windows.Forms.Form
$form.Text = 'Admin Tools'
foreach ($i in 0..2) {
$btn = New-Object System.Windows.Forms.Button
$btn.Text = "Option $i"
$btn.Top = 30 * $i
$btn.Add_Click({ $this.Text = 'Clicked!' }) # only the button you click changes
$form.Controls.Add($btn)
}
$form.ShowDialog() | Out-NullNote that Register-ObjectEvent is different. Its -Action block uses $Sender and $EventArgs, not $this.
Gotchas worth remembering
- Classes need
$thisexplicitly. A bare$Nameis never the property. - No
$thisin static methods. There’s no instance for it to point at. - Not in the pipeline. In
ForEach-ObjectandWhere-Objectyou want$_. - Anywhere else, it’s
$null. No error, just nothing. - Don’t assign to it. It’s automatic. Treat it as read-only.
- ScriptProperties run on every read. Keep them cheap. Don’t put AD lookups or network calls in one.
- The setter’s new value is
$args[0].
Once it clicks that $this is simply “the object this code belongs to”, the rest follows. If you’re in a pipeline, it’s $_. If you’re inside an object, it’s $this.