Working with .NET Objects & COM Directly
PowerShell Intermediate/Advanced
Chapter 5 · Working with .NET Objects & COM Directly
Every object this entire course has ever touched — a process, a file, an error record — has secretly been a .NET object the whole time, per Fundamentals 5's own opening finding. Every one of them arrived pre-built, courtesy of a cmdlet. This chapter is about building and calling into .NET directly, without a cmdlet doing it for you first — plus its considerably older cousin, COM, which comes with a real, painful cleanup responsibility no other chapter in this course has needed to mention.
Creating Objects: New-Object vs. [Type]::new()
Both lines create the identical type of object — [Type]::new() is simply the more direct, modern way to say the same thing.
Static vs. Instance Members
[Math] exposes only static members — there's no such thing as "an instance of Math," which is why you'll never see New-Object Math or [Math]::new() anywhere. The [Type]::Member syntax always means "call this directly on the type"; a variable's own .Member always means "call this on the specific object that variable holds."
A Real Payoff: StringBuilder vs. String Concatenation at Scale
Every += against a string doesn't modify it in place — it can't, because .NET strings are immutable. Each one silently builds an entirely new string containing a full copy of everything accumulated so far, plus the new piece. At 10,000 iterations that's not 10,000 small operations; it's roughly 10,000 progressively larger copies, real quadratic-ish cost that gets noticeably slow well before the loop ends. StringBuilder avoids all of that by mutating an internal buffer directly. The [void] cast on AppendLine isn't decoration — Append/AppendLine both return the StringBuilder itself (for chaining), and per Fundamentals 7's own central rule, an uncaptured return value would otherwise silently join the loop's own output.
Other Useful .NET Types
[System.IO.Path] is generally more robust than hand-built string concatenation for paths — it handles separator characters correctly regardless of trailing slashes on the inputs. [regex], already covered in Chapter 2, is the same idea applied to pattern matching — both are ordinary .NET types, reached exactly the way [Math] and [System.Text.StringBuilder] are here.
COM Objects: New-Object -ComObject
COM (Component Object Model) predates .NET entirely, and it isn't part of the managed object model this chapter has covered so far — New-Object -ComObject is the only way in; there's no [Type]::new() equivalent, because a COM class isn't a .NET type in the first place.
The COM Cleanup Gotcha: Ghost Processes
EXCEL.EXE, here) can keep running as an orphaned background process even after $excel goes out of scope and the script finishes. Run a script like this repeatedly without .Quit(), and Task Manager quietly accumulates one ghost EXCEL.EXE process per run — invisible (since Visible only controlled the window, not the underlying process's lifetime the moment it's abandoned), silently consuming memory, never asked for.
.Quit() (or the more general Marshal.ReleaseComObject()) every time, on every COM object you create, with no automatic safety net behind you. Skipping it doesn't error, doesn't warn, and doesn't fail the script — it just quietly leaves something running that should have stopped.
try/finally (Fundamentals 9) with the cleanup calls inside finally — that guarantees .Quit() runs even if something in the middle of the script throws, which is exactly the situation most likely to leave a ghost process behind in practice.
Hands-On Exercises
Explain the real performance difference between building a 10,000-line string with += in a loop versus using StringBuilder, using this chapter's own explanation of string immutability.
Explain why [Math]::Pow(2, 10) works with no object creation step first, while $sb.Append("x") requires $sb to already exist as a real, created object. What's the underlying distinction?
Explain why forgetting to call $excel.Quit() after New-Object -ComObject Excel.Application can leave EXCEL.EXE running in the background indefinitely, even after the script finishes and $excel goes out of scope.
Chapter 5 Quick Reference
New-Object TypeName/[Type]::new()— two ways to create a .NET object;[Type]::new()is the newer, generally preferred syntax- Static members (
[Type]::Member) — called on the type itself, no object required; e.g.[Math]::Pow(),[Math]::PI - Instance members (
$object.Member) — called on a specific, already-created object StringBuilder— mutates a buffer in place; far cheaper than repeated+=on an immutable string at scale[System.IO.Path]::Combine()— robust path-joining, correct separators regardless of trailing slashesNew-Object -ComObject— the only way to create a COM object; no[Type]::new()equivalent exists for COM- COM cleanup is manual —
.Quit()and/orMarshal.ReleaseComObject(); skipping it can leave a ghost process (e.g.EXCEL.EXE) running indefinitely, with no automatic garbage collection to rescue you - Next chapter: CIM/WMI: Querying and Managing Windows Systems