Archives for the Month of October, 2006

Load a Custom DLL from PowerShell

One of the amazing features of PowerShell is its amazing reach.  You can interact with the file system, the registry, certificate stores, COM, WMI, XML, cmdlets, providers … the list seems to go on forever.

One important extension point is the ability to seamlessly interact with .NET DLLs. These are most commonly shipped in SDKs, and many people have made use of this.  For example:

When pre-built API DLLs don't suit your need, you have yet another option -- writing your own! If you are comfortable with any of the .NET languages, it is quite simple to compile a DLL and load it into your PowerShell session.

Take, for example, a simple math library.  It has a static Sum method, and an instance Product method:

namespace MyMathLib
{
  public class Methods
  {
    public Methods()
    {
    }
    public static int Sum(int a, int b)
    {
      return a + b;
    }

    public int Product(int a, int b)
    {
      return a * b;
    }
  }
}

Here is everything required to get that working in your PowerShell system:

[C:\temp]

PS:25 > notepad MyMathLib.cs

 

(…)

 

[C:\temp]

PS:26 > csc /target:library MyMathLib.cs

Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42

for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727

Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.

 

 

[C:\temp]

PS:27 > [Reflection.Assembly]::LoadFile("c:\temp\MyMathLib.dll")

 

GAC    Version        Location

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

False  v2.0.50727     c:\temp\MyMathLib.dll

 

 

 

[C:\temp]

PS:28 > [MyMathLib.Methods]::Sum(10, 2)

12

 

[C:\temp]

PS:29 > $mathInstance = new-object MyMathLib.Methods

Suggestion: An alias for New-Object is new

 

[C:\temp]

PS:30 > $mathInstance.Product(10, 2)

20

Email Quoting and Wrapping in 59 Bytes

Here's a useful bit of line noise that I came up with when I wanted to put some quoted text into email.  Starting with unwrapped text in the clipboard:

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

It generates:

[D:\Temp]
PS:30 > pclip | %{[Regex]::Split($_,'(.{0,60}(?:\s|$))')}|%{if($_){"> $_"}}
> Lorem ipsum dolor sit amet, consectetur adipisicing elit,
> sed do eiusmod tempor incididunt ut labore et dolore magna
> aliqua. Ut enim ad minim veniam, quis nostrud exercitation
> ullamco laboris nisi ut aliquip ex ea commodo consequat.
> Duis aute irure dolor in reprehenderit in voluptate velit
> esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
> occaecat cupidatat non proident, sunt in culpa qui officia
> deserunt mollit anim id est laborum.

(pclip is the clipboard paste utility available from http://unxutils.sourceforge.net/)

Edit: Here's a version that's 16 characters shorter using V2's -split operator!

(pclip)-split'(.{0,60}(?:\s|$))'|?{$_}|%{"> $_"}

Have any densely useful snippets you'd like to share?

Get-HelpMatch – Search Help (Apropos) in PowerShell

To give a glimpse into the writing process behind my upcoming "Windows PowerShell - The Definitive Guide" (O'Reilly,) I'll occasionally post entries "as the author sees it."  The first in this series shows how to search the PowerShell help content for a phrase or pattern.

 

Program: Get-HelpMatch¶

Both the Get-Command and Get-Help cmdlets allow you to search for command names that match a given pattern. However, when you don't know exactly what you are looking for, you will more often have success searching through the help content for an answer. On Unix systems, this command is called Apropos.  Similar functionality does not exist as part of the PowerShell's help facilities, however.¶

That doesn't need to stop us, though, as we can write the functionality ourselves.¶

To run this program, supply a search string to the Get-HelpMatch script. The search string can be either simple text, or a regular expression. The script then displays the name and synopsis of all help topics that match. To see the help content for that topic, use the Get-Help cmdlet.¶

Example 1-4. Get-HelpMatch.ps1¶

##############################################################################¶

## Get-HelpMatch.ps1¶

##¶

## Search the PowerShell help documentation for a given keyword or regular¶

## expression.¶

## ¶

## Example:¶

##    Get-HelpMatch hashtable¶

##    Get-HelpMatch "(datetime|ticks)"¶

##############################################################################¶

 ¶

param($searchWord = $(throw "Please specify content to search for"))¶

 ¶

$helpNames = $(get-help *)¶

 ¶

foreach($helpTopic in $helpNames)¶

   $content = get-help -Full $helpTopic.Name | out-string¶

   if($content -match $searchWord)¶

  

      $helpTopic | select Name,Synopsis¶

  

 

Counting Lines of Source Code in PowerShell

Oren Eini recently ran into some performance problems while using PowerShell to count the number of lines in a source tree:

I wanted to know how many lines of code NHibernate has, so I run the following PowerShell command...
(gci -Recurse | select-string . ).Count
The result:
(Graphic of PowerShell still working after about 5 minutes, using 50% of his CPU.)
Bummer.

The performance problem from this command comes from us preparing for a rich pipeline experience in PowerShell that you never use.  With only a little more text, you could have run even more powerful reports:

Line count per path:
   gci . *.cs -Recurse | select-string . | Group Path
Min / Max / Averages:
   gci . *.cs -Recurse | select-string . | Group Filename | Measure-Object Count -Min -Max -Average
Comment ratio: 
   $items = gci . *.cs -rec; ($items | select-string "//").Count / ($items | select-string .).Count

But if you don't need that power, there are alternatives that perform better.  Let's look at some of them.  We'll use a baseline of the command that Oren started with:

[C:\temp]
PS:14 > $baseline = Measure-Command { (gci . *.cs -Recurse | Select-String .).Count }

… and a comparison to the LineCount.exe he pointed to:

PS:15> $lineCountExe = Measure-Command { C:\temp\linecount.exe *.cs /s }
PS:16 > $baseline.TotalMilliseconds / $lineCountExe.TotalMilliseconds
41.5567286307833

(The Select-String approach is about 41.5x slower)

Since we don't need all of the PowerShell metadata generated by Select-String, and we don' t need the Regular Expression matching power of Select-String, we can instead use the [File]::ReadAllText() method from the .NET Framework:

PS:17 > $readAllText = Measure-Command { gci . *.cs -rec | % { [System.IO.File]::ReadAllText($_.FullName) } | Measure-Object -Line }
PS:18 > $readAllText.TotalMilliseconds / $lineCountExe.TotalMilliseconds
3.30927987204783

This is now about 3.3x slower – but is only 87 characters!  With a PowerShell one-liner, you were able to implement an entire linecount program.
If you want to go further, you can write a linecount program yourself:

## Get-LineCount.ps1
## Count the number of lines in all C# files in (and below)
## the current directory.

function CountLines($directory)
{
    $pattern = "*.cs"
    $directories = [System.IO.Directory]::GetDirectories($directory)
    $files = [System.IO.Directory]::GetFiles($directory, $pattern)

    $lineCount = 0

    foreach($file in $files)
    {
        $lineCount += [System.IO.File]::ReadAllText($file).Split("`n").Count
    }

    foreach($subdirectory in $directories)
    {
        $lineCount += CountLines $subdirectory
    }

    $lineCount
}

CountLines (Get-Location)

Now, about 2.7x slower – but in an easy to read, easy to modify format that saves you from having to open up your IDE and compiler.

PS:19 > $customScript = Measure-Command { C:\temp\Get-LineCount.ps1 }
PS:20 > $customScript.TotalMilliseconds / $lineCountExe.TotalMilliseconds
2.73733204860216

And to nip an annoying argument in the bud:

## Get-LineCount.rb
## Count the number of lines in in all C# files in (and below)
## the current directory

require 'find'

def filelines(file)
  count = 0
  while line = file.gets
     count += 1
  end
  count
end

def countFile(filename)
    file = File.open(filename)
    totalCount = filelines(file)
    file.close()
    totalCount
end   

totalCount = 0

files = Dir['**/*.cs']
files.each { |filename| totalCount += countFile(filename) }

puts totalCount

Which gives:

PS:21 > $rubyScript = Measure-Command { C:\temp\Get-LineCount.rb }
PS:22 > $rubyScript.TotalMilliseconds / $lineCountExe.TotalMilliseconds
3.0709602651302

Updated: PowerShell Profile Locations Rationale

You may have noticed that we changed the location of the default profiles yet again! 

The one in "My Documents" is now under "WindowsPowerShell," rather than "PsConfiguration."
The one in "All User's Documents" is now under the installation root.

Since you are probably wondering why, I've updated my last explanation: The Story Behind the Naming and Location of PowerShell Profiles.

Great PowerShell Screencasts

Several people have been churning out screencasts lately, and they are really great resources.

On the Channel9 front, David Aiken recently posted two DFO Show screencasts:

In addition, Doug Finke and the Lab49 team just posted an overview of some PowerShell features – Variables, Arrays, Hashtables, Functions, and PsObject.

For interactive learners, the screencast medium is a great resource.  Not only do you get to watch over somebody's shoulder, but the vocal accompaniment also provides the "Proximity Effect" benefit of working under the guidance of an expert.

DasBlog Upgrade to 1.9 Complete!

Now that DasBlog 1.9 has been released, I've finally upgraded.  Everything appears to have gone well, and I'm really happy with some of the new themes available.  You won't necessarily notice this if you only read the site through your RSS reader, so why don't you come on down and check out the new look? 🙂

Please let me know if you see anything broken!