Using Proxy Commands in PowerShell

Jeffrey Snover just posted a great article on how to use Proxy Commands in CTP3. He also built a module called MetaProgramming that makes this much easier. I was able to take what he did and created a proxy command for get-childitem and added two switch parameters, -containersOnly and -NoContainersOnly.

I posted the code up on PoshCode here.

  1. Function Get-ChildItemProxy {
  2. [CmdletBinding(DefaultParameterSetName='Items', SupportsTransactions=$true)]
  3. param(
  4.     [Parameter(ParameterSetName='Items', Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
  5.     [System.String[]]
  6.     ${Path},
  7.  
  8.     [Parameter(ParameterSetName='LiteralItems', Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)]
  9.     [Alias('PSPath')]
  10.     [System.String[]]
  11.     ${LiteralPath},
  12.  
  13.     [Parameter(Position=1)]
  14.     [System.String]
  15.     ${Filter},
  16.  
  17.     [System.String[]]
  18.     ${Include},
  19.  
  20.     [System.String[]]
  21.     ${Exclude},
  22.  
  23.     [Switch]
  24.     ${Recurse},
  25.  
  26.     [Switch]
  27.     ${Force},
  28.  
  29.     [Switch]
  30.     ${Name},
  31.    
  32.     [Switch]
  33.     ${ContainersOnly},
  34.    
  35.     [Switch]
  36.     ${NoContainersOnly}
  37.     )
  38.  
  39. begin
  40. {
  41.     try {
  42.         $outBuffer = $null
  43.         if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer) -and $outBuffer -gt 1024)
  44.         {
  45.             $PSBoundParameters['OutBuffer'] = 1024
  46.         }
  47.         $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Get-ChildItem', [System.Management.Automation.CommandTypes]::Cmdlet)
  48.        
  49.         if ($ContainersOnly)
  50.         {
  51.             [Void]$PSBoundParameters.Remove("ContainersOnly")
  52.             $scriptCmd = {& $wrappedCmd @PSBoundParameters | Where-Object {$_.PSIsContainer -eq $true}}
  53.            
  54.         } elseif ($NoContainersOnly)
  55.                {
  56.                    [Void]$PSBoundParameters.Remove("NoContainersOnly")
  57.                    $scriptCmd = {& $wrappedCmd @PSBoundParameters | Where-Object {$_.PSIsContainer -eq $false}}
  58.                }   
  59.         {
  60.             $scriptCmd = {& $wrappedCmd @PSBoundParameters }
  61.         }
  62.        
  63.  
  64.        
  65.         $steppablePipeline = $scriptCmd.GetSteppablePipeline()
  66.         $steppablePipeline.Begin($PSCmdlet)
  67.     } catch {
  68.         throw
  69.     }
  70. }
  71.  
  72. process
  73. {
  74.     try {
  75.         $steppablePipeline.Process($_)
  76.     } catch {
  77.         throw
  78.     }
  79. }
  80.  
  81. end
  82. {
  83.     try {
  84.         $steppablePipeline.End()
  85.     } catch {
  86.         throw
  87.     }
  88. }
  89. <#
  90.  
  91. .ForwardHelpTargetName Get-ChildItem
  92. .ForwardHelpCategory Cmdlet
  93.  
  94. #>
  95.  
  96. }
downloadThis Script brought to you by PoshCode
 

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.