Chapter 20. Loading .NET Libraries and Compiling Code

by Mar 28, 2012

Since PowerShell is layered on the .NET Framework, you already know from Chapter 6 how you can use .NET code in PowerShell to make up for missing functions. In this chapter, we’ll take up this idea once again. You’ll learn about the options PowerShell has for creating command extensions on the basis of the .NET Framework. You should be able to even create your own cmdlets at the end of this chapter.

Topics Covered:

Loading .NET Libraries

Many functionalities of the .NET Framework are available right in PowerShell. For example, the following two lines suffices to set up a dialog window:

Add-Type -assembly Microsoft.VisualBasic
[Microsoft.VisualBasic.Interaction]::MsgBox("Do you agree?", "YesNoCancel,Question", "Question")

In Chapter 6, you learned in detail about how this works and what an “assembly” is. PowerShell used Add-Type to load a system library and was then able to use the classes from it to call a static method like MsgBox().

That’s extremely useful when there is already a system library that offers the method you’re looking for, but for some functionality even the .NET Framework doesn’t have any right commands. For example, you have to rely on your own resources if you want to move text to the clipboard. The only way to get it done is to access the low-level API functions outside the .NET Framework.

Creating New .NET Libraries

As soon as you need more than just a few lines of code or access to API functions to implement the kinds of extensions you want, it makes sense to write the extension directly in .NET program code. The following example shows how a method called CopyToClipboard() might look in VB.NET. The VB.NET code is assigned to the $code variable as plain text:

$code = @'
Imports Microsoft.VisualBasic
Imports System
Namespace ClipboardAddon
  Public Class Utility
    Private Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Integer) As Integer
    Private Declare Function EmptyClipboard Lib "user32" () As Integer
    Private Declare Function CloseClipboard Lib "user32" () As Integer
    Private Declare Function SetClipboardData Lib "user32"(ByVal wFormat As Integer, ByVal hMem As Integer) As Integer
    Private Declare Function GlobalAlloc Lib "kernel32" (ByVal wFlags As Integer, ByVal dwBytes As Integer) As Integer
    Private Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Integer) As Integer
    Private Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Integer) As Integer
    Private Declare Function lstrcpy Lib "kernel32" (ByVal lpString1 As Integer, ByVal lpString2 As String) As Integer

    Public Sub CopyToClipboard(ByVal text As String)
      Dim result As Boolean = False
      Dim mem As Integer = GlobalAlloc(&H42, text.Length + 1)
      Dim lockedmem As Integer = GlobalLock(mem)
      lstrcpy(lockedmem, text)
      If GlobalUnlock(mem) = 0 Then
        If OpenClipboard(0) Then
          EmptyClipboard()
          result = SetClipboardData(1, mem)
          CloseClipboard()
        End If
      End If
    End Sub
  End Class
End Namespace
'@

You have to first compile the code before PowerShell can execute it. Compilation is a translation of your source code into machine-readable intermediate language (IL). There are two options here.

In-Memory Compiling

To compile the source code and make it a type that you can use, feed the source code to Add-Type and specify the programming language the source code used:

$type = Add-Type -TypeDefinition $code -Language VisualBasic

Now, you can derive an object from your new type and call the method CopyToClipboad(). Done!

$object = New-Object ClipboardAddon.Utility
$object.CopyToClipboard(Hi Everyone!)

You might be wondering why in your custom type, you needed to use New-Object first to get an object. With MsgBox() in the previous example, you could call that method directly from the type.

CopyToClipboard() is created in your source code as a dynamic method, which requires you to first create an instance of the class, and that’s exactly what New-Object does. Then the instance can call the method.

Alternatively, methods can also be static. For example, MsgBox() in the first example is a static method. To call static methods, you need neither New-Object nor any instances. Static methods are called directly through the class in which they are defined.

If you would rather use CopyToClipboard() as a static method, all you need to do is to make a slight change to your source code. Replace this line:

Public Sub CopyToClipboard(ByVal text As String)

Type this line instead:

Public Shared Sub CopyToClipboard(ByVal text As String)

Once you have compiled your source code, then you can immediately call the method like this:

[ClipboardAddon.Utility]::CopyToClipboard(Hi Everyone!)

DLL Compilation

With Add-Type, you can even compile and generate files. In the previous example, your source code was compiled in-memory on the fly. What if you wanted to protect your intellectual property somewhat and compile a DLL that your solution would then load?

Here is how you create your own DLL (make sure the folder c:powershell exists, or else create it or change the output path in the command below):

PS> $code = @'
Imports Microsoft.VisualBasic
Imports System
Namespace ClipboardAddon
  Public Class Utility
    Private Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Integer) As Integer
    Private Declare Function EmptyClipboard Lib "user32" () As Integer
    Private Declare Function CloseClipboard Lib "user32" () As Integer
    Private Declare Function SetClipboardData Lib "user32"(ByVal wFormat As Integer, ByVal hMem As Integer) As Integer
    Private Declare Function GlobalAlloc Lib "kernel32" (ByVal wFlags As Integer, ByVal dwBytes As Integer) As Integer
    Private Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Integer) As Integer
    Private Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Integer) As Integer
    Private Declare Function lstrcpy Lib "kernel32" (ByVal lpString1 As Integer, ByVal lpString2 As String) As Integer

    Public Shared Sub CopyToClipboard(ByVal text As String)
      Dim result As Boolean = False
      Dim mem As Integer = GlobalAlloc(&H42, text.Length + 1)
      Dim lockedmem As Integer = GlobalLock(mem)
      lstrcpy(lockedmem, text)
      If GlobalUnlock(mem) = 0 Then
        If OpenClipboard(0) Then
          EmptyClipboard()
          result = SetClipboardData(1, mem)
          CloseClipboard()
        End If
      End If
    End Sub
  End Class
End Namespace
'@

PS> Add-Type -TypeDefinition $code -Language VisualBasic -OutputType Library `
>>
-OutputAssembly c:powershellextension.dll

After you run these commands, you should find a file called c:powershellextension.dll with the compiled content of your code. If not, try this code in a new PowerShell console. Your experiments with the in-memory compilation may have interfered.

To load and use your DLL from any PowerShell session, go ahead and use this code:

PS> Add-Type -Path C:powershellextension.dll
PS> [Clipboardaddon.utility]::CopyToClipboard("Hello World!")

You can even compile and create console applications and windows programs that way – although that is an edge case. To create applications, you better use a specific development environment like Visual Studio.