Inline F# in PowerShell

We can use C#  quite easily with the Add-Type Cmdlet. With the -language parameter, you can also use VB .NET. However,if you are into functional programming and like F#, you can use that as well, although not quite as easily.

First, go and download the September 2008 CTP of F#. Once you have this installed, launch the 32 bit version of PowerShell. I'm using a 64 bit Vista machine and I had problems with the 64 bit version of Powershell with one line which I will get to in a bit. I think it has to do with the F# CodeDom Provider, not with PowerShell itself.

The F# CodeDom.dll was installed in C:\Program Files (x86)\FSharp-1.9.2.9\bin for me. Your mileage may vary. Anyway, cd into the bin directory of F# and you will find a file called FSharp.Compiler.CodeDom.dll. Once you are there, you can run the following lines to load up the F# Code Provider.

Add-Type -Path FSharp.Compiler.CodeDom.dll
$provider = New-Object Microsoft.FSharp.Compiler.CodeDom.FSharpCodeProvider
$fsharpCode = @"
let sample = [1;2;3;4;5;6;7]
"@
$fsharpType = Add-Type -TypeDefinition $fSharpCode -CodeDomProvider $provider -PassThru | 
where { $_.IsPublic }
$fsharpType::sample

When you call Sample on $fsharpType, it will return the array with numbers 1 through 7.

For some reason when I ran this on a 64 bit I get the following error when I try to add the type with typeDefinition $fsharpCode on line 6

Add-Type : The system cannot find the file specified
At line:6 char:23
+ $fsharpType = Add-Type <<<<  -TypeDefinition $fSharpCode -CodeDomProvider $provider -PassThru | where { $_.IsPublic }
    + CategoryInfo          : NotSpecified: (:) [Add-Type], Win32Exception
    + FullyQualifiedErrorId : System.ComponentModel.Win32Exception,Microsoft.PowerShell.Commands.AddTypeCommand

If you are into this sort of thing, have fun!

ISE &#8211; Comment out a block of text

In a comment in an earlier post, reader Bernd asks the following question:

Now I'm guessing if there is a way to use $psise.CustomMenu to add CommentBlock and UnCommentBlock commands.

One way to tackle this would be the following.

$text = $psise.CurrentOpenedFile.editor.SelectedText                                                                                                        
$psise.CurrentOpenedFile.editor.InsertText("<# $text #>")      

Once you have this you can wrap it in a function and use Custom Menus to create a “Comment Block” Menu item.

Hope this helps.

Andy

Code Snippets in PowerShell ISE

Granted, PowerShell doesn’t always come with all the features you need right out of the box. However, because we have the power of .NET and an awesome scripting language, we can pretty much do whatever we need to do and we don’t have to wait for the PS team to ship V-Next.

The PowerShell team has taken this philosophy into the development of the ISE as well. In a couple previous posts here and here, I have talked about some very basic customizations using the $PSISE variable that comes with the Scripting Integrated Environment.

One thing I love about PS V2 is Advanced Functions. The only bummer is there is a lot of boilerplate text for each one. This sounds like a perfect opportunity for a code snippet. We can add some functions to our $PROFILE in the PowerShell ISE to make this happen.

First of all, we need to open our $profile script. It probably doesn’t exist so you can do the following. Note that the ISE is a PS host and it has its own profile, different than the one for PowerShell.exe that you use in Console.Exe.

new-item -type file -force $profile

Now we can open it in the ISE with the following line

$psise.CurrentOpenedRunspace.OpenedFiles.Add($profile) 

Now that we have the $profile open, we can create a function that will insert some text into the file that is currently being edited.

function Insert-Text 
{
param(
    [parameter(Mandatory=$true, ValueFromPipeline=$true]
    [string]
    $text
    )
$currentFilePath = $psise.CurrentOpenedFile.FullPath
$currentFile = $psIse.CurrentOpenedRunspace.OpenedFiles | 
               where {$_.FullPath -eq $currentFilePath}
$currentFile.editor.InsertText($text)
}

This function will insert text into the current script at the cursor’s location.

Now we need to write the actual snippet, which can also be a function

function New-FunctionTemplate
{
$f = @"
function Verb-Noun {
param (
    [parameter(Mandatory=$true, ValueFromPipeline=$true)
    [string]
    $p1
)
begin {}
process {}
end {}
}
"@
Insert-Text -text $f
}

The last line uses the Insert-Text function that we just created and inserts the snippet that is stored in the HereString $f.

Now we need to put it together and create a shortcut key and a new custom menu to insert this text.

$psIse.CustomMenu.Submenus.Add("_Function Template", {New-FunctionTemplate}, "Alt+F")

For this particular instance, I chose the name New FunctionTemplate and used Alt F for the keyboard shortcut.

FunctionTemplate

More Integrated Scripting Environment Customization

You can add custom menus using $PSISE in the PowerShell Integrated Scripting Environment. Quite simply, you create a custom menu, assign it a scriptblock, and a keyboard shortcut. For example, lets say you wanted Ctrl-D to always run the dir (get-childitem) command.

You can use the CustomMenu class like so.

   1: $psIse.CustomMenu.Submenus.Add("_Dir", {dir}, "Ctrl+D")

In addition to the keyboard shortcut, you also get a menu as well.

CustomMenu

Now when you hit Ctrl-D, it will run the dir command. All kinds of cool things are available within the editor with the new object model.

Add-Type can use C# 3.0 Syntax

There may be, on some occassion, a need to create a class with a set of properties in PowerShell. This can’t be done natively, but it can be done with some very simple C# syntax, thanks to C# version 3.0, which is really a new compiler as opposed to a new version of .NET. The base class library is the same. Here is a very basic class with four properties.

 

   1: PS C:\Users\andy.schneider> $csharp = @"
   2: >> public class Andy
   3: >> {
   4: >>  public int age {get;set;}
   5: >>  public string firstName {get;set;}
   6: >>  public string lastName {get;set;}
   7: >>  public string blog {get;set;}
   8: >> }
   9: >> "@
  10: >>
  11: PS C:\Users\andy.schneider>

But we still have to get it into PowerShell. We can use the Add-Type Cmdlet to do this, but it will fail unless we use the C# 3.0 compiler under the covers. We can specify the language we are using with the –Language parameter. I know CSharp30 is wrong, but I like to give it garbage so it will tell me what the valid languages are. Because it takes an ENUM we can find out that CsharpVersion3 is a valid language.

 

   1: PS C:\Users\andy.schneider>
   2: PS C:\Users\andy.schneider> add-type $csharp -Language Csharp30
   3: Add-Type : Cannot bind parameter 'Language'. Cannot convert value "Csharp30" to type "Microsoft.PowerShell.Commands.Lan
   4: guage" due to invalid enumeration values. Specify one of the following enumeration values and try again. The possible e
   5: numeration values are "CSharp, CSharpVersion3, VisualBasic, JScript".
   6: At line:1 char:27
   7: + add-type $csharp -Language <<<<  Csharp30
   8: PS C:\Users\andy.schneider> add-type $csharp -Language CsharpVersion3

All right, now we are ready to to after running the command on line 8.

We can create a new object of type Andy and set it to $andy

   1: PS C:\Users\andy.schneider> $andy = new-object andy
   2: PS C:\Users\andy.schneider> $andy | gm
   3:  
   4:  
   5:    TypeName: Andy
   6:  
   7: Name        MemberType Definition
   8: ----        ---------- ----------
   9: Equals      Method     System.Boolean Equals(Object obj)
  10: GetHashCode Method     System.Int32 GetHashCode()
  11: GetType     Method     System.Type GetType()
  12: ToString    Method     System.String ToString()
  13: age         Property   System.Int32 age {get;set;}
  14: blog        Property   System.String blog {get;set;}
  15: firstName   Property   System.String firstName {get;set;}
  16: lastName    Property   System.String lastName {get;set;}

Now we have a full blown object with properties and we can create new instances of them all day long, and the C# was pretty darn straight forward.

Industrial Strength Functions in V2 Part 2

Jeffrey Snover just published a great article on using the  capabilities of Advanced Functions. They have changed the name from Script Cmdlets to Advanced Functions and completely gotten rid of the Cmdlet keyword.

This actually makes a lot of sense IMHO. One of the beauties of this is that there is a smooth path from a basic function to an advanced function

   1: PS C:> function foo {"foo $args"}
   2: PS C:> foo bar
   3: foo bar
   4: PS C:> function foo ($a) {"foo $a"}
   5: PS C:> foo -a bar
   6: foo bar
   7: PS C:> function foo {param($a) "foo $a"}
   8: PS C:> foo -a bar
   9: foo bar
  10: PS C:> function foo {param([Parameter(ValueFromPipeline=$true)]$a) "foo $a"}
  11: PS C:> "bar" | foo
  12: foo bar
  13: PS C:> foo bar
  14: foo bar
  15: PS C:> foo -a bar
  16: foo bar
  17: PS C:>

Just by setting some attributes on a parameter, you can get all kinds of great functionality for free.

Jeffrey’s function “Test-LeapYear” is a great example or template to start with. I really like how he used multi-line comments for all the Help documentation. Very slick.

Industrial Strength Functions in V2 Part 2

Jeffrey Snover just published a great article on using the  capabilities of Advanced Functions. They have changed the name from Script Cmdlets to Advanced Functions and completely gotten rid of the Cmdlet keyword.

This actually makes a lot of sense IMHO. One of the beauties of this is that there is a smooth path from a basic function to an advanced function

   1: PS C:\> function foo {"foo $args"}
   2: PS C:\> foo bar
   3: foo bar
   4: PS C:\> function foo ($a) {"foo $a"}
   5: PS C:\> foo -a bar
   6: foo bar
   7: PS C:\> function foo {param($a) "foo $a"}
   8: PS C:\> foo -a bar
   9: foo bar
  10: PS C:\> function foo {param([Parameter(ValueFromPipeline=$true)]$a) "foo $a"}
  11: PS C:\> "bar" | foo
  12: foo bar
  13: PS C:\> foo bar
  14: foo bar
  15: PS C:\> foo -a bar
  16: foo bar
  17: PS C:\>

Just by setting some attributes on a parameter, you can get all kinds of great functionality for free.

Jeffrey’s function “Test-LeapYear” is a great example or template to start with. I really like how he used multi-line comments for all the Help documentation. Very slick.

Industrial Strength Functions in V2 Part 2

Jeffrey Snover just published a great article on using the  capabilities of Advanced Functions. They have changed the name from Script Cmdlets to Advanced Functions and completely gotten rid of the Cmdlet keyword.

This actually makes a lot of sense IMHO. One of the beauties of this is that there is a smooth path from a basic function to an advanced function

   1: PS C:> function foo {"foo $args"}
   2: PS C:> foo bar
   3: foo bar
   4: PS C:> function foo ($a) {"foo $a"}
   5: PS C:> foo -a bar
   6: foo bar
   7: PS C:> function foo {param($a) "foo $a"}
   8: PS C:> foo -a bar
   9: foo bar
  10: PS C:> function foo {param([Parameter(ValueFromPipeline=$true)]$a) "foo $a"}
  11: PS C:> "bar" | foo
  12: foo bar
  13: PS C:> foo bar
  14: foo bar
  15: PS C:> foo -a bar
  16: foo bar
  17: PS C:>

Just by setting some attributes on a parameter, you can get all kinds of great functionality for free.

Jeffrey’s function “Test-LeapYear” is a great example or template to start with. I really like how he used multi-line comments for all the Help documentation. Very slick.

Module Manifests in CTP3

Module Manifests are wonderful things. Basically, you can set up all kinds of dependencies and create versioning with them, among many other great features.

I just wanted to get a quick example out to show how you can create them. First, you need a module. To create a module, just rename a script from .PS1 to .PSM1.

Here's a very simple PSM1 file

38 >  cat foobar.psm1
function foo {"foo"}
function bar {"in bar and calling foo";foo}
export-modulemember bar

Note the "export-modulemembr cmdlet. This cmdlet says which functions are public and available to the user that imports this module.

Now we can create a new module manifest using the New-ModuleManifest Cmdlet.

39 >  new-modulemanifest
 
cmdlet New-ModuleManifest at command pipeline position 1
Supply values for the following parameters:
Path: foobar.psd1
NestedModules[0]: foobar.psm1
NestedModules[1]:
Author: Andy Schneider
CompanyName: Get-PowerShell
Copyright: All Rights reserved
Description: PowerShell Modules Rock
TypesToProcess[0]:
FormatsToProcess[0]:
RequiredAssemblies[0]:
OtherFiles[0]:

One trick to note here, even though you are prompted for a bunch of information, not all of these parameters are required. If you don't want to enter a NestedModule, you can just hit enter and it will prompt you for the next parameter.

This generates a PSD1 file which is just a hash table. Here's what this auto-generated one looks like

44 >  cat .\foobar.psd1
#
# Module manifest for module 'foobar'
#
# Generated by: Andy Schneider
#
# Generated on: 12/22/2008
#
 
@{
 
# These modules will be processed when the module manifest is loaded.
NestedModules = 'foobar.psm1'
 
# This GUID is used to uniquely identify this module.
GUID = '27c7c84b-dbce-4919-8494-aa4eb4646915'
 
# The author of this module.
Author = 'Andy Schneider'
 
# The company or vendor for this module.
CompanyName = 'Get-PowerShell'
 
# The copyright statement for this module.
Copyright = 'All Rights reserved'
 
# The version of this module.
ModuleVersion = '1.0'
 
# A description of this module.
Description = 'PowerShell Modules Rock'
 
# The minimum version of PowerShell needed to use this module.
PowerShellVersion = '2.0'
 
# The CLR version required to use this module.
CLRVersion = '2.0'
 
# Functions to export from this manifest.
ExportedFunctions = '*'
 
# Aliases to export from this manifest.
ExportedAliases = '*'
 
# Variables to export from this manifest.
ExportedVariables = '*'
 
# Cmdlets to export from this manifest.
ExportedCmdlets = '*'
 
# This is a list of other modules that must be loaded before this module.
RequiredModules = @()
 
# The script files (.ps1) that are loaded before this module.
ScriptsToProcess = @()
 
# The type files (.ps1xml) loaded by this module.
TypesToProcess = @()
 
# The format files (.ps1xml) loaded by this module.
FormatsToProcess = @()
 
# A list of assemblies that must be loaded before this module can work.
RequiredAssemblies = @()
 
# Lists additional items like icons, etc. that the module will use.
OtherItems = @()
 
# Module specific private data can be passed via this member.
PrivateData = ''
 
}
 
45 >

Now finally we can import the module and run get-module to see what we have

 

   1: 45 >  import-module .\foobar.psd1
   2: 46 >  get-module *foo*
   3:  
   4:  
   5: Name              : C:\Users\andys\Documents\Scripts\foobar.psd1
   6: Path              : C:\Users\andys\Documents\Scripts\foobar.psd1
   7: Description       : PowerShell Modules Rock
   8: Guid              : 27c7c84b-dbce-4919-8494-aa4eb4646915
   9: Version           : 1.0
  10: ModuleBase        : C:\Users\andys\Documents\Scripts
  11: ModuleType        : Manifest
  12: PrivateData       :
  13: AccessMode        : ReadWrite
  14: ExportedAliases   : {}
  15: ExportedCmdlets   : {}
  16: ExportedFunctions : {[bar, bar]}
  17: ExportedVariables : {}
  18: NestedModules     : {foobar.psm1}
  19:  
  20:  
  21:  
  22: 47 >  foo
  23: The term 'foo' is not recognized as a cmdlet, function, operable program, or script file. Verify the term and try again
  24: .
  25: At line:1 char:4
  26: + foo <<<<
  27:     + CategoryInfo          : ObjectNotFound: (foo:String) [], CommandNotFoundException
  28:     + FullyQualifiedErrorId : CommandNotFoundException
  29:  
  30: 48 >  bar
  31: in bar and calling foo
  32: foo
  33: 49 >

Note the Exported Functions property. When I created this Module Manifest, I said that it required foobar.psm1 by specifying a Nested Module. Also remember that I only exported the function bar, not foo, in foobar.psm1. You can see that foo does not work but bar runs swimmingly, and can even call into foo, even though the user doesn't have access to that function.

This is really only the beginning when it comes to Modules.

Industrial Strength Functions in PowerShell V2 CTP 3


With PowerShell V2 we can now get nearly all the functionality of the PowerShell engine that is available to Cmdlet developers that are using C#. Granted, if you haven’t written any Cmdlets in C#, you probably don’t understand the power that this brings, but trust me, its huge!

Here are just a couple things to think about as we start to build more industrial strength functions. We can do all kinds of cool things with parameters now. We can validate that the entry matches a regular expression. We can tell the parameter to accept a value from the pipeline. We can declare whether or not a parameter is mandatory or not.

In addition to tons of flexibility with parameters, we can also leverage the built in help for functions that we write. Rather than going on and listing every single feature, let me just give an example of what I am talking about.

   1: Function Get-EvenNumber {
   2: #.Synopsis
   3:     #    Returns even numbers using the Modulo operator
   4: #.Description
   5:     #    takes a set of numbers and tells you which ones are even
   6: #.Parameter number
   7:     #    An integer that is required, can take values from the pipeline as well   
   8: #.Example
   9:     #    1..10 | Get-EvenNumber
  10:  
  11: param (
  12:    [Parameter(Position=0, 
  13:               Mandatory=$true, 
  14:               ValueFromPipeline=$true, 
  15:               HelpMessage="Please type a number between 1 and 10")]
  16:    [Alias("integer")]
  17:    [int]
  18:    $number
  19:  
  20: )
  21: begin {"Begining of Pipeline"}
  22:  
  23: process {
  24:          if (($number % 2) -eq 0) {"$number is even"}
  25:         }
  26:  
  27: end {"End of Pipeline"}
  28:   
  29: }

Now here are some screen shots of how you can use this function, just like a Cmdlet in version 1.

In this first one, we first use the function with a parameter, and then in the pipeline.

In the function, we used the Begin, Process, and End Blocks, so we can do stuff before we send anything down the pipeline, then work on the pipeline objects, and then do anything else we need to do to wrap up. This is why you see the text “Beginning Pipeline” and “End of Pipleline".” This is for demonstration purposes only.

image

Because we declared that the number parameter is mandatory and we specified a help message, we get this great interactivity for only setting a couple attributes. PowerShell tells the user, “You forgot this mandatory paramter, and if you need help, type !? and you can see what the function author wants you to know about the parameter. Pretty flippin’ awesome! 

image

And finally , of course we get the following when we type help get-evennumber because of all the great code commenting we did at the beginning of the function. Notice how nicely its formatted. It even automagically pulls all the parameters and their types for you in the SYNTAX section.

image