You need to hear $this

PowerShell & AutomationPublished 19 Jun 2015· Updated 26 Sept 2026· 5 min read
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 runsWhat $this points at
A class method or constructorThe 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 elseNothing. 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
  • $this is the object that owns the code that’s running
  • $args is 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.

powershell
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: True

One 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.

powershell
$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 Smith

Adding a method to an object

Same idea, but a ScriptMethod does something rather than returning a value.

powershell
$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     # Running

A 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 $_.

powershell
$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'  # throws

That’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.

powershell
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, SizeMB

GUI 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.

powershell
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-Null

Note that Register-ObjectEvent is different. Its -Action block uses $Sender and $EventArgs, not $this.

Gotchas worth remembering

  • Classes need $this explicitly. A bare $Name is never the property.
  • No $this in static methods. There’s no instance for it to point at.
  • Not in the pipeline. In ForEach-Object and Where-Object you 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.

Further reading