Exercise 4: Managed Code from PowerShell v2

Figure 1

In this exercise you will become familiar with the different ways in which you can invoke managed code from within PowerShell. You will start by running a few commands to query the static methods from various .Net classes and work yourself up to invoking static methods on custom C# code in order to carry out operations that would otherwise be slower when using PowerShell.

Part 1: The Basics

Figure 2

  1. Open a PowerShell v2 CTP3 prompt by accessing:

    Start All Programs Accessories Windows PowerShell Windows PowerShell
  2. PowerShell v2 is can invoke static methods and properties from the .Net Framework. Let’s focus on the System.DateTime class. Let’s see the different methods and properties available b typing:

    PowerShell

    [System.DateTime] | gm –static
    Note:
    All the static methods and properties will be displayed:

    Figure 3

  3. Try invoking a static method:

    PowerShell

    [System.DateTime]::IsLeapYear(2009)
  4. PowerShell can also create .Net objects. Let’s create a date object by typing:

    PowerShell

    $d = new-object System.DateTime 2009,11,11
  5. Anything that can be done to a .Net object, can be carried out in PowerShell:

    PowerShell

    $d - [System.DateTime]::now
  6. Try the following to add some days to the .Net object:

    PowerShell

    $d.AddDays(-11)
  7. The new PowerShell allows you to embed any .Net language. Let’s define a new type based on a predefined class. To start, open the cs1.ps1 file found in the following path:

    C:\Server 2008 R2 Labs\PowerShell v2 for Developers\Exercise-4\cs1.ps1
    Note:
    The contents of the file are displayed below:

    PowerShell

    Add-Type @" namespace t1 { public class Point { public int x; public int y; public Point(int x, int y) { this.x = x; this.y = y; } public override string ToString() { return string.Format("POINT({0}, {1})", x,y); } public void Double() { x = x*2; y = y*2; } } } "@
    Note:
    The command Add-Type is used to declare the type that follows in the C# code. The C# code is a simple Point class. It has a constructor, and overridden ToString method and a Double function.
  8. Switch back to the PowerShell window and change directories to the one that holds the script:

    PowerShell

    cd “C:\Server 2008 R2 Labs\PowerShell v2 for Developers\Exercise-4”
  9. Invoke the script by typing:

    PowerShell

    .\cs1.ps1
  10. The type is now defined and be freely used. Let’s create a Point by typing:

    PowerShell

    $p = New-Object T1.Point 10,10
  11. Let’s check the contents of $p:

    PowerShell

    $p | ft –auto
    Note:
    The x and y members should both have the value of 10
  12. Let’s invoke the Double method:

    PowerShell

    $p.Double()
  13. And find out if the method was indeed called:

    PowerShell

    $p.ToString()
  14. Finally, let’s try to invoke a method from an unmanaged DLL via C# from PowerShell using Add-Type:

    PowerShell

    $WND = add-type -passthru -name test1 -member '[DllImport("user32.dll", CharSet=CharSet.Auto)]public static extern uint MessageBox(IntPtr hWnd, String text, String caption, uint type);'

    Now that you have an instance of the type, try the following:

    • Create a message box that displays the message “Hello from PowerShell v2!” and has a Window Title of “PowerShell Rocks!”:

    Figure 4

Part 2: Create a large example XML file

Figure 5

Note:
PowerShell has the ability to export objects to an XML file. In this part of the exercise you will create a large XML file using PowerShell. You will use that file later on the exercise.

Note:
The following exercise must be carried out on your server machine.

  1. Open a PowerShell v2 CTP3 prompt by accessing:

    Start All Programs Accessories Windows PowerShell Windows PowerShell
  2. Change directories to the working directory of this exercise by typing:

    PowerShell

    cd “C:\Server 2008 R2 Labs\PowerShell v2 for Developers\Exercise-4”
    Note:
    To make a large XML file you will create one that contains the references for all the DLLs in the system directory in Windows, and its subdirectories.
  3. Use the following two commands for this:

    PowerShell

    $sysdir = (Get-Content env:SystemRoot) + "\system32" Get-ChildItem $sysdir -recurse -filter *.dll -errorAction silentlyContinue | Export-Clixml samplefile.xml
    Note:
    Creating the file can take a few seconds. The system directory contains a very large list of DLL files. The results will be exported to the file named samplefile.xml.

Part 3: Using PowerShell to parse XML file

Figure 6

Note:
Now that you have a large XML file, you can easily use PowerShell to parse it and query some of its data. In this example we will query from the XML the list of file names that start with a vowel, and sort it. To make it a more valuable performance test, we will repeat this task 10 times and measure the time that it takes in total.

  1. To edit the scripts that will be created, we will be using PowerShell’s ISE. In case you do not have it open, do so by accessing:

    Start All Programs Accessories Windows PowerShell Windows PowerShell ISE
  2. On the script pane, add the code that will handle the iterations and measure the time:

    PowerShell

    $totalIterations = 10
    FakePre-2b1e933bd6904956b8acddf5f9c888d3-18ad7ff45c9946da80271d027ed7ee89FakePre-4ba01985d1124353aae3f46aa95de20f-5c7f83c2f1454a93baa8314595900031FakePre-33e7d8935a4743c1b756e9a30071d204-237c65f4c8414506aed0f92cac9651fdFakePre-b1e4442cb9794907bf728130e7245e4b-ce2e0263d1f5479b8b618e3c1895afc2FakePre-9c74d1f4e4024328b35e1248eada39f4-e36ba566705844269e7f1ed5865fead2FakePre-5d2d9eaa24da4bc8a9dbf1252268a8a9-7015d9ea2d0c408cad7fdbbc342a8c57FakePre-ee08b14aa8184b0ea5ce29c8e54f96e7-c9f1eac3b09440f9a38814307a18545b# Query the XML here and set the $filescount # Print message hereFakePre-200681b1184d4815ba2188299a5759c8-619d146c42534de89ab2911bd65602d3FakePre-d7be0869649d480788e144ec784a123b-60b76d79d0ea4c2e9fd9ef9938b0b2a4FakePre-0c401f2805f8412584beceffb8ef934a-9d3893fc0c7a4a94ac0f1be7e774c17eFakePre-fe78c567e4c04f4a9747794ca40c0ff7-3277097efd36425abda4a112f10fa2a5FakePre-30ac7f95f19a49988e9a34eea7fde24f-9741e6efdd8d42c4a814b34a43b0c21cFakePre-71660c80e0a245e182eb0d051bbdadce-9dd09da2ee504395882dd4fd711031fc# Query the XML here and set the $filescount # Print message here
  3. Where you see the comment for the querying of the XML, add the instructions that will get the count of file names found in the list that start with a vowel. The instructions are:

    PowerShell

    $vowels = "a", "e", "i", "o", "u" $fileswithvowels = Import-Clixml samplefile.xml | Where {$vowels -contains $_.Name[0]} | Select Name | Sort Name $filescount = $fileswithvowels.Count
    Note:
    As you can see it is very easy to query the list of names that start with a vowel and sort it in PowerShell, all with very few instructions and even without any knowledge of the format of the XML because it was created by PowerShell in its own format.
  4. At this point all you need is a printed message that indicates how many files were found, how many times, and how long the entire process took. At the end of the code add the line that will show the message (highlighted below):

    PowerShell

    $after = Get-Date
    FakePre-00bedd3b4b964f19a3c277e3a3ab83df-f5395a1b51c944129e2a78ff3c19d978FakePre-9870bc65ce854c4e8e127a92c3d59acd-555e2d636e05410383361f4639a898f2FakePre-6d4c6009712c47e1b1f4840c7976d4db-f03af6c53eab48d0b03d7aa4e836e1f1FakePre-5df812f862f4434ea387fce5b9f64da3-ff35bf38904c4327b6d1ec35b19c26d7# Print message here Write-Host PowerShell found a list of $filescount files $totalIterations times in $totalTime.ToString()
  5. Save the file as:

    C:\Server 2008 R2 Labs\PowerShell v2 for Developers\Exercise-4\PowerShellTestParse.ps1
  6. From the command pane, run the following command and hit enter:

    PowerShell

    Set-ExecutionPolicy RemoteSigned
    Note:
    This command allows you to run scripts. PowerShell restricts the execution of scripts by default for safety.
  7. From the command pane, run the following command to change directories to our current working directory:

    PowerShell

    cd “C:\Server 2008 R2 Labs\PowerShell v2 for Developers\Exercise-4\”
  8. To run the script type the following command in the command pane and hit Enter:

    PowerShell

    .\PowerShellTestParse.ps1
    Note:
    After a few seconds you should see the output of the script:

    Figure 7

    Note:
    In our case the execution lasted almost 49.77 seconds.

Part 4: Using C# to parse XML file

Figure 8

Note:
PowerShell runs on top of .Net, and therefore you could always implement the previous part using XML objects from the .Net Framework. But now you also have the option to write code directly in C# and use it in your PowerShell scripts. This not only has the benefit of giving you the option to recycle some of your C# routines, but it can in some cases help improve the performance. In this exercise you will put that theory to a test.

The following C# code performs the exact same query previously done in PowerShell, but it uses XPath to select the correct nodes in the XML.

C#

using System; using System.Xml; using System.Xml.XPath; namespace TestParse { public class CsharpTestParse { public static void Run() { const int totalIterations = 10; int iteration = totalIterations; TimeSpan totalTime = new TimeSpan(); int filesCount = 0; while (iteration > 0) { DateTime before = DateTime.Now; XPathDocument sampleDoc = new XPathDocument("samplefile.xml"); XPathNavigator navigator = sampleDoc.CreateNavigator(); XmlNamespaceManager nsMgr = new XmlNamespaceManager(new NameTable()); nsMgr.AddNamespace("tns", "https://schemas.microsoft.com/powershell/2004/04"); XPathExpression expression = navigator.Compile( "/tns:Objs/tns:Obj/tns:Props/tns:S[@N=\"Name\" and (starts-with(.,'a') " + "or starts-with(.,'e') or starts-with(.,'i') or starts-with(.,'o') " + "or starts-with(.,'u') or starts-with(.,'A') or starts-with(.,'E') " + "or starts-with(.,'I') or starts-with(.,'O') or starts-with(.,'U'))]/text()"); expression.AddSort(".", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Text); expression.SetContext(nsMgr); XPathNodeIterator iterator = navigator.Select(expression); filesCount = iterator.Count; DateTime after = DateTime.Now; totalTime = totalTime.Add(after.Subtract(before)); iteration--; } Console.WriteLine("C# found a list of " + filesCount + " files " + totalIterations + " times in " + totalTime); } } }

  1. The code is loaded in the file called CsharpTestParse.cs. In order to use this class, you must first load the contents of this file to a string. To do that, execute the following two commands in the command window of the PowerShell ISE:

    PowerShell

    $csharpversion = Get-Content .\CsharpTestParse.cs $csharpversion = [string]::Join([Environment]::NewLine, $csharpversion)
    Note:
    The Get-Content cmdlet returns an array of strings, where each string represents a line in the file. The second line above takes all the lines and joins them into a single string.
  2. At this point $csharpversion should contain the code. In order to load this C# class we will use the new Add-Type cmdlet from PowerShell v2. Use the following command:

    PowerShell

    Add-Type –ReferencedAssemblies “System.Xml” –TypeDefinition $csharpversion
    Note:
    By default Add-Type interprets C# and imports the System namespace. We add the extra reference for the System.Xml.dll library so that it can be used by the C# snippet.
  3. Copy the sample.xml file to the following directory:

    C:\Users\student1
  4. Everything should be ready to run the C# sample. Make the following call:

    PowerShell

    [TestParse.CsharpTestParse]::Run()
  5. After a few seconds you should see the same results that you saw earlier in PowerShell, but the time will probably be a lot shorter:

    Figure 9

    Note:
    In our test, the result was obtained in almost 7 seconds. That’s a huge difference when compared to the 46 seconds that it took for us when using PowerShell directly.

    Note:
    Please wait for the Instructor to resume the lecture.

Exercise-4: Conclusions

Figure 10

The Add-Type cmdlet lets you add .NET Framework types to Windows PowerShell from the source code of another .NET Framework language.

Add-Type compiles the source code that creates the types and generates assemblies that contain the new .NET Framework types. Then, you can use the .NET Framework types in Windows PowerShell commands along with the standard object types provided by the .NET Framework.

You can also use Add-Type to load assemblies into your session so that you can use the types in the assemblies in Windows PowerShell.

Add-Type allows you develop new .NET Framework types, to use .NET Framework types in C# libraries, and to access Win32 APIs.