Working with the Component Object Model (COM) - Advanced PowerShell - PowerShell in Depth, Second Edition (2015)

PowerShell in Depth, Second Edition (2015)

Part 4. Advanced PowerShell

One of PowerShell’s greatest strengths is its ability to connect to other technologies, such as WMI, CIM, COM, .NET, and a host of other acronyms. In part 4, we’ll briefly look at each of these and demonstrate how PowerShell can use them. We’ll provide one caution: We don’t dive as deeply into these as we have the other topics in this book. That’s because they are external technologies. Although accessed from within PowerShell, they can also be used from a variety of other places, and each could quite easily consume an entire book (and in some cases, others have already written those books—which we’ll mention as appropriate within the chapters). Our focus in these chapters will be to look at how PowerShell can use these technologies, give you a starting place for doing so, and then give you direction for further independent exploration.

Having said that, these additional technologies provide a mass of functionality that’ll be of great benefit to you in automating tasks in your environment. We encourage you to learn these additional techniques.

Chapter 34. Working with the Component Object Model (COM)

This chapter covers

· Discovering what COM is and isn’t

· Working with COM objects

Get ready for a blast from the past! Before Microsoft invented the .NET Framework, folks relied on an earlier technology called the Component Object Model (COM). COM is basically a set of rules that enable developers to write software components that can easily interoperate. COM is still in wide use today, although it’s considered an older cousin to the .NET Framework. Many technologies you rely on, and use with PowerShell, are still based on COM. Examples include Active Directory Service Interfaces (ADSI) for working with Active Directory, WMI, and the object models that enable you to script against Internet Explorer or the Office products such as Word and Excel. COM is here and unlikely to go away in the foreseeable future. Unlike .NET, whose components can’t run without the .NET Framework itself installed, COM doesn’t have any specific prerequisites—many pieces of COM software can run on any Windows computer.

Because so much functionality was written in COM, the .NET Framework originally shipped with—and still contains—the ability to load and use those pieces of software that comply with the COM specification. This is done through a .NET Framework layer called the interop layer. PowerShell, through interop, is able to take advantage of many pieces of COM-based software.

34.1. Introduction to COM objects

COM software is generally packaged into a dynamic link library (DLL) and is usually written in C++. A given DLL can contain one or more distinct components, usually referred to as COM objects. These COM objects are referred to by unique program identifiers (ProgIDs). You don’t generally need to know what DLL a COM object lives in or where that DLL is stored. Instead, you ask Windows to load the object by providing Windows with the desired ProgID, like wscript.shell or word.application. Windows then consults its Registry, which contains cross-references for ProgIDs and their physical DLL locations. Thinking about that process for a moment will tell you an unfortunate fact about COM objects: They have to be explicitly installed and registered with Windows so that Windows knows what ProgIDs they use and where the necessary DLL files are kept. This is one of the biggest upsides to COM, because you can look in a single location—the Registry—and see what COM objects are available. It’s also one of the biggest downsides, because DLLs must be explicitly registered before being used.

Note

The process of creating and installing COM DLLs is beyond the scope of this book. Registration is also out-of-bounds for this discussion, although we’ll note that many COM-compliant DLLs can be registered by right-clicking them in Windows Explorer and selecting the Register option. You can usually also run the Regsvr32.exe command-line utility, providing it with the complete path and filename of a COM-compliant DLL, to register the COM objects in that DLL.

You can also discover many of the ProgIDs that are available like this:

Get-WmiObject -Class Win32_ProgIDSpecification |

sort ProgID | select ProgID, Caption

We counted 709 ProgIDs on one of our Windows 8.1 test machines, so expect to spend some time tracking down the exact ProgIDs you’ll need.

It isn’t enough to know what COM objects are available; you also need to know how to use each one. Many COM-compliant DLLs ship with a type library, sometimes stored in a TLB (type library) file, which describes how the COM objects inside that DLL work. Without a type library, you’re completely on your own—in fact, for the purposes of this chapter, we’re going to assume that any COM object you want to work with comes with a type library. Without one, we’d definitely have to stray far into the realm of C++ development, which is further than we plan, or want, to go with this book.

Ultimately, you’re either going to find out about COM object ProgIDs by seeing other people’s examples or run across them by using a type library browser. Some commercial script editors—SAPIEN PowerShell Studio is one example—include type library browsers; you can also use your favorite internet search engine to look for “type library browser” or “type library explorer” to find other commercial and free options. These browsers usually display a list of ProgIDs and can be expanded to show the members associated with a ProgID.

What else is out of scope?

There’s a lot more that we could cover when it comes to COM, including Distributed COM (DCOM). DCOM, which is needed for WMI calls to remote machines, is the technology that lets you connect to COM objects on remote machines. But we’re not going to cover it.

Ultimately, we feel that using COM is something you’ll only do when there’s not a better, more PowerShell-native option available. In many cases, using COM within PowerShell forces you to adopt a very programmer-like approach, because COM predates PowerShell’s pipeline and other command line–oriented approaches. Further, as Microsoft continues to invest in PowerShell, they’ll provide all of the functionality you used to get from COM objects as friendlier, better-documented PowerShell commands, providers, and so forth.

COM is, from an administrative viewpoint, extremely incomplete. COM lived at the height of an era where the GUI was king and where automatable components—like COM objects—were very much a second-class effort within Microsoft and other companies. It’s rare to find a COM object that can do everything you might need it to do; one of the primary drivers behind PowerShell’s existence, in fact, was the scattershot approach known as COM.

Another problem with COM is that .NET’s interop layer isn’t perfect—and, therefore, PowerShell’s ability to use COM objects isn’t perfect. Some objects just won’t work, forcing you to fall back to an older, COM-based technology like VBScript in order to accomplish your automation goals.

We know a lot of readers may find themselves using a COM object now and again to accomplish some crucial task. That’s okay—we know you have to do what you have to do to get the job done. But using COM is stepping firmly outside of PowerShell; you’re not using PowerShell at all, except as an access mechanism to the older technology. Because it’s outside PowerShell, it’s outside what we’re going to spend a lot of time on in this book.

There’s no global, consolidated list of COM objects—even of the ones made by Microsoft. Most—even Microsoft’s—are poorly documented, if they’re documented at all. Choosing to use COM is choosing to enter the “here there be dragons” portion of the computing map. You’ll often be on your own, at best helped by examples posted by other folks in various online forums and blogs.

Our purpose with this chapter isn’t to provide a comprehensive look at everything COM can do. For this chapter, we’re assuming you know a few things:

· The ProgID of the COM object you want to use

· The members—that is, the properties and methods—of the COM object that you want to use

· The general functional approach of the COM object you want to use

Our goal with this chapter is to show you how to put that knowledge to use. Because many of the COM-based examples you’re likely to find online are based in VBScript, we’ll provide some VBScript equivalents to the necessary PowerShell commands, with the hope that our doing so will make those examples easier to translate into PowerShell.

One useful resource for translating VBScript examples into PowerShell is the “The VBScript-to-Windows PowerShell Conversion Guide,” which can be found at http://technet.microsoft.com/en-us/library/ee221101.aspx. It does move on a frequent basis so be prepared to search.

Note

If you’re converting WMI-based VBScripts to PowerShell, do not, under any circumstances, follow the methods of displaying the results you’ll see in those scripts. Remember that PowerShell emits objects and that the format cmdlets are there to handle your output requirements.

But how do you use COM objects directly in PowerShell?

34.2. Instantiating COM objects in PowerShell

Instantiating is the act of loading an object, in this case a COM object, into memory, storing a reference to that object in a variable, and generally preparing to use the COM object.

In VBScript, instantiation is done by using the CreateObject() method and the COM object’s ProgID:

Obj = CreateObject("Wscript.Network")

PowerShell’s command looks similar:

$Obj = New-Object –ComObject "WScript.Network"

The –ComObject parameter tells PowerShell to fall back to the old COM way of doing things, rather than trying to create an instance of a .NET Framework class. If the New-Object command fails, there are several possible reasons to consider:

· The COM object you’ve selected isn’t compatible with PowerShell. There’s no real fix for this.

· The ProgID you specified isn’t in the Registry, meaning the COM object isn’t installed or isn’t registered.

· The COM object can’t be instantiated in this fashion; it must be created as the result of running some other COM object’s methods. You’ll need to consult the object’s documentation—if you can find it—to learn more.

Tip

If you know the ProgID, your best bet if you run into problems is to hop on a search engine and enter that ProgID as a search term. You’ll often turn up examples, and possibly even documentation, that way.

Once the object is instantiated and referenced by a variable, you’re ready to use it.

34.3. Accessing and using COM objects’ members

Like the other objects you’ve used in PowerShell, COM objects have members—primarily properties and methods. Properties may be read-only or may be writable; methods cause the object to execute some task or behavior. One thing you’ll see quite commonly when working with COM objects is that properties are in reality other embedded COM objects. At this stage you’re digging into the documentation and hoping there are some methods on the object to help you unravel all of this.

Properties are accessed by referring to the object variable and the property name. In VBScript, you’d do this to display the UserName property of the WScript.Network object:

objNetwork = CreateObject("WScript.Network")

WScript.Echo objNetwork.UserName

Performing the same trick in PowerShell looks almost exactly the same. You use Write-Host to output the property’s contents to the screen, because that’s what VBScript’s WScript.Echo does:

$Network = New-Object –Com WScript.Network

Write-Host $Network.UserName

Note

The obj prefix on variable names—such as objNetwork—comes from a coding convention called Hungarian Notation that was popular during VBScript’s heyday. Today, most developers prefer not to use those prefixes, although you can obviously do whatever you like. In our PowerShell example, we omitted the obj prefix, but we could’ve called our variable $objNetwork and the example would’ve worked the same.

Methods are executed in much the same fashion. For example, WScript.Network has a MapNetworkDrive method that accepts two parameters: the drive letter to use and the Universal Naming Convention (UNC) path to map the drive to. In VBScript, assuming objNetwork already contained the object, you’d do this:

objNetwork.MapNetworkDrive("z:\","\\server\share")

Once again, PowerShell’s syntax looks much the same:

$Network.MapNetworkDrive("z:\","\\server\share")

There’s one important difference between VBScript and PowerShell. With VBScript, you could omit the parentheses from a method that didn’t accept any parameters. For example, let’s suppose you had an object in the variable objMine, and it had a method named DoSomething, which accepted no input arguments. In VBScript, this would be legal:

objMine.DoSomething

That’s not legal in PowerShell. In PowerShell, you must always provide parentheses immediately after the method name, with no space after the method name, even if the method requires or accepts zero arguments:

$mine.DoSomething()

That’s an important distinction to remember if you’re translating VBScript code to PowerShell. One advantage that PowerShell does offer, if you have to resort to using COM objects, is that you can interact with them without turning to scripting. Want to know what properties and methods$Network has but your VBScript-foo is a little shaky? Ask PowerShell:

PS C:\> $network | Get-Member

TypeName: System.__ComObject#{24be5a31-edfe-11d2-b933-00104b365c9f}

Name MemberType Definition

---- ---------- ----------

AddPrinterConnection Method void AddPrinterConnection (string...

AddWindowsPrinterConnection Method void AddWindowsPrinterConnection ...

EnumNetworkDrives Method IWshCollection EnumNetworkDrives ()

EnumPrinterConnections Method IWshCollection EnumPrinterConnect...

MapNetworkDrive Method void MapNetworkDrive (string, str...

RemoveNetworkDrive Method void RemoveNetworkDrive (string, ...

RemovePrinterConnection Method void RemovePrinterConnection (str...

SetDefaultPrinter Method void SetDefaultPrinter (string)

ComputerName Property string ComputerName () {get}

Organization Property string Organization () {get}

Site Property string Site () {get}

UserDomain Property string UserDomain () {get}

UserName Property string UserName () {get}

UserProfile Property string UserProfile () {get}

Unfortunately details may not be as forthcoming as you might wish:

PS C:\> $Network.MapNetworkDrive.OverloadDefinitions

void MapNetworkDrive (string, string, Variant, Variant, Variant)

If you want to use this method, there are some expected values, but there’s no clue as to what they should be. You just have to know or get lucky with an example of someone else’s code.

As we’ve said, we’re hoping you only need COM objects to fill a specific need. But don’t think they have to exist alone. Once an object exists in PowerShell, you can use it any way you need:

PS C:\> $fso=New-Object -Com Scripting.FileSystemObject

PS C:\> $fso.drives | where {$_.drivetype -eq 2} |

>> Select Path,@{Name="SizeGB";Expression={

>> "{0:N2}" -f ($_.TotalSize/1GB)}},

>> @{Name="FreeGB";Expression={ "{0:N2}" -f ($_.FreeSpace/1GB)}},

>> @{Name="UsedGB";Expression={

>> "{0:N2}" -f (($_.TotalSize - $_.FreeSpace)/1GB)}}

>>

Path SizeGB FreeGB UsedGB

---- ------ ------ ------

C: 19.90 4.40 15.50

D: 4.00 2.72 1.28

PowerShell can use the COM object in a pipelined expression just as well as any other type of object.

34.4. PowerShell and COM examples

This section won’t supply anything like a complete tutorial on using COM objects with PowerShell, but it’ll provide a few examples to get you started.

We’ll start by looking at the FileSystem object. This object was seen a lot in VBScript and still has a few uses. For instance, you can directly see the size of a folder, as the following listing shows.

Listing 34.1. Using the FileSystem object

$data = @()

$fso = New-Object -ComObject "Scripting.FileSystemObject"

$top = $fso.GetFolder("c:\test")

$data += New-Object PSObject -Property @{

Path = $top.Path

Size = $top.Size

}

foreach ($sf in $top.SubFolders) {

$data += New-Object PSObject -Property @{

Path = $sf.Path

Size = $sf.Size

}

}

$data

You instantiate the FileSystem object and use the GetFolder method on the top-level folder. The SubFolders collection is iterated to find the folders and their sizes.

We stated in the introduction that the Microsoft Office products have a COM object model associated with them. The one exception is pre–Office 2013 versions of OneNote, which is XML based. The Office 2013 version of OneNote has a COM-based object model available; seehttp://msdn.microsoft.com/en-us/library/office/jj680118(v=office.15).aspx for details.

Note

It’s possible to work with the OpenXML format for Word documents, but in many ways this is much harder and has even less documentation and fewer examples. As of this writing, OpenXML isn’t fully compatible with PowerShell v3 or v4, and we don’t recommend you use it until this is remedied.

Creating and writing to a Word document can be achieved using the code in listing 34.2.

Listing 34.2. Creating a Word document

$word = New-Object -ComObject "Word.application"

$word.visible = $true

$doc = $word.Documents.Add()

$doc.Activate()

$word.Selection.Font.Name = "Cambria"

$word.Selection.Font.Size = "20"

$word.Selection.TypeText("PowerShell")

$word.Selection.TypeParagraph()

$word.Selection.Font.Name = "Calibri"

$word.Selection.Font.Size = "12"

$word.Selection.TypeText("The best scripting language in the world!")

$word.Selection.TypeParagraph()

Working with Word in this way quickly gets tedious. You’ll most likely want to create functions to handle some of the grunt work. One great use for these techniques is documenting your servers. Use WMI to gather the configuration information and write straight into a Word document. Instant, painless documentation—the admin’s dream.

The last example we want to show (listing 34.3) involves Excel.

Listing 34.3. Creating an Excel worksheet

$xl = New-Object -comobject "Excel.Application"

$xl.visible = $true

$xlbooks =$xl.workbooks

$wkbk = $xlbooks.Add()

$sheet = $wkbk.WorkSheets.Item(1)

## create headers

$sheet.Cells.Item(1,1).FormulaLocal = "Value"

$sheet.Cells.Item(1,2).FormulaLocal = "Square"

$sheet.Cells.Item(1,3).FormulaLocal = "Cube"

$sheet.Cells.Item(1,4).FormulaLocal = "Delta"

$row = 2

for ($i=1;$i -lt 25; $i++){

$f = $i*$i

$sheet.Cells.Item($row,1).FormulaLocal = $i

$sheet.Cells.Item($row,2).FormulaLocal = $f

$sheet.Cells.Item($row,3).FormulaLocal = $f*$i

$sheet.Cells.Item($row,4).FormulaR1C1Local = "=RC[-1]-RC[-2]"

$row++

}

If you run this, you’ll see that it runs painfully slowly. You can watch each entry being made. If you want data in Excel, create a CSV file using Export-CSV and then load the file into Excel.

Note

If you’re looking for more examples of integrating Office applications with PowerShell, visit Jeff’s blog and go to http://jdhitsolutions.com/blog/2012/05/san-diego-2012-powershell-deep-dive-slides-and-demos/.

34.5. Summary

COM is a technology that you need to be aware of, but it isn’t something you’ll be using every day unless you’re performing a lot of automation based on the Office suite or have a legacy application with a COM interface that you can script. It’s a legacy technology that nevertheless will be with us for some time due to the vast number of COM-based applications that have been produced. If you have a .NET-based alternative, we recommend that you use that instead. But in either event, it’s all about the objects, and PowerShell makes this a much easier proposition.