Tome's Land of IT

IT Notes from the Powertoe – Tome Tanasovski

Category Archives: Modules

Announcing PshOdata – Cmdlets that make Odata easy

I’m sitting in the PowerShell summit and just left the stage where I presented how the Odata extensions work and shared my new project (contributors welcome) to bring PowerShell-driven Odata endpoints to the masses

To make it clear just how easy it is to use, here is the code required to create a web service around get-process and stop-process.

$class = New-PshOdataClass Process -PK ID -Properties 'Name','ID'
$class |Set-PshOdataMethod -verb get -cmdlet get-process -Params Name, ID -FilterParams Name
$class |Set-PshOdataMethod -verb delete -cmdlet stop-process -FilterParams ID
$class |New-PshOdataEndpoint

This will generate three files in a folder called odata that can be copied up to your IIS server. After an IISReset, the Process endpoint will be available via the following URLs:

I hope you enjoy, and please post issues on GitHub if you encounter any.

Github Project

Advertisements

You Know PowerShell is an Object-Oriented Language, Right?

I was recently reading an article that I’ll refrain from linking to for fear of a flame war. It infuriated me because it referred to PowerShell as a procedural/functional language that is antiquated and more like shell scripting.  I reflected on this for a while and I came to the realization that if I were to look at 99.99999% of the code out there I may think the same thing.  I also realized that there are reasons that PowerShell code appears procedural instead of object oriented.

This post will do a few things

  1. Show you how to do object-oriented programming with PowerShell using a traditional approach
  2. Show you the alternative (still object-based) so you can see why it’s generally used

A Class

Let’s describe a dog.

Properties

A dog has a name, color, and is some size.

$dogclass = new-object psobject -Property @{
   color = $null
   name = $null
   size = $null
}

A Constructor

While it may not be needed because you can instantiate the object without doing so (via psobject.copy()), object-oriented developers love their constructors. It’s also a good place to add some validation for your class. For example, in the below constructor we’ll restrict the size to either small, medium, or large.

function DogClass {
    param(
          [Parameter(Mandatory=$true)]
          [String]$name,
          [Parameter(Mandatory=$false)]
          [string]$color,
          [Parameter(Mandatory=$false)]
          [ValidateSet('Small','Medium','Large')]
          [String]$size
    )
    $dog = $DogClass.psobject.copy()
    $dog.name = $name
    $dog.color = $color
    $dog.size = $size
    $dog
}

Now you can start using your class

08:31:50 PS C:\> $lucy = DogClass -name Lucy -color brown -size large
08:45:07 PS C:\> $lucy

color                                       name                                        size
-----                                       ----                                        ----
brown                                       Lucy                                        large


A Method

A dog performs certain functions. For example, a dog is known to pee. We can modify our dog class with a method by using Add-Member. Also note that you can use the $this special variable to access the object that is invoking the method.

$DogClass |Add-Member -MemberType ScriptMethod -Name "Pee" -Value {
    "A warm refreshing pee trickles out of {0}" -f $this.name
}

With the method created, you can instantiate Lucy again from the modified class and access her new method.

08:50:50 PS C:\> $lucy = DogClass -name Lucy -color brown -size large
08:52:30 PS C:\> $lucy.Pee()
A warm refreshing pee trickles out of Lucy

Accessor Functions

Accessor functions help you protect the properties of an object. As in Perl and Python, there is no real protected or private property available to you. Therefore, you can use the same convention that these other languages use to supply an underscore prefix to any private methods or properties. It doesn’t actually prevent people from using it, but it’s a clear sign that they shouldn’t use it. For example, if we wanted to make the size property private, we would modify the class to look like this. Note: I’m adding the scriptmethod we created in one step using the -PassThru parameter of Add-Member.

$DogClass = new-object psobject -Property @{
   color = $null
   name = $null
   _size = $null
} |Add-Member -PassThru -MemberType ScriptMethod -Name "Pee" -Value {
    "A warm refreshing pee trickles out of {0}" -f $this.name
}

ScriptMethod

With the new _size property, it’s easy enough to modify our constructor to use the new property, but what if you want to control how people set or get the property of size. You can create an accessor function to do this. Basically, this is just a method that will set or return the data from _size.

$dogclass |Add-Member -PassThru -MemberType ScriptMethod -Name Size -Value {
    param(
        [Parameter(Mandatory=$false, Position=0)]
        $Size
    )
    if ($size) {
        $this._size = $size
    } else {
        $this._size
    }
}

Now, we can access the data in _size by using the accessor.

$lucy = DogClass -name Lucy -color brown -size large
"The dog is {0}" -f $lucy.Size()

We can also set the _size using the same function.

$lucy.Size('medium')

It’s important to note that I lost the property validator I have in the constructor. ScriptMethods break if you have an optional parameter that also has a validator. There are two ways to handle this. Either you add validation to the Size() method or you create two accessor functions, SetSize() and GetSize(). Both are acceptable and both are very easy to implement. Here’s an example of implementing your own validatior

ScriptProperty

A nice way to allow access to the pseudo private properties is to use the ScriptProperty member instead. This provides you with a way to perform a scripted action for a get, but it also allows you to do validation on a set. The difference is that it allows you to use the equal sign instead of a method to set the property.

Here’s what a ScriptProperty looks like. The first ScriptBlock is the get function and the second ScriptBlock is the set function.

$dogclass |Add-Member -MemberType ScriptProperty -name Size -Force -Value {
    $this._size
} {
    param(
        $Size
    )
    if (@('small','medium','large') -contains $size) {
        $this._size = $size
    } else {
        throw "This is not a valid size.  A size must be small, medium, or large"
    }
}

I’m using Force above to override the Size member I already created. In order to use the new property

In order to use this property, I can now do the following with my instantiated instance of Lucy.

PS C:\> $lucy.Size = 'blah'
Exception setting "Size": "This is not a valid size.  A size must be small, medium, or large"
At line:1 char:1
+ $lucy.Size = 'blah'
+ ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], SetValueInvocationException
    + FullyQualifiedErrorId : ScriptSetValueRuntimeException

PS C:\> $lucy.Size = 'medium'
PS C:\> $lucy.Size
medium

A nice thing about this technique is that your custom formatters will be able to see the property. In other words, it can be used in Select, Format-Table, Format-List, etc.

Class Inheritance

PowerShell doesn’t have real inheritance. However, the functionality of inheritance is very easily achieved. If you’re not familiar with inheritance, it’s pretty straightforward. Many times a class will have subclasses. For instance, a Scottish Terrier is a subclass of dog that is always small, black, and prefers to chase rats (rather than hunt like a beagle or keep your feet warm like a pug). Therefore, we can create a Scotty class that is based on the dog class, but has these properties set by default and has a method for hunting vermin.

Inherited Class

Because the definition of our class is simply an object, we can copy and modify it to create a new class that can be used later by other things.

$ScottyClass = $DogClass.psobject.copy()
$ScottyClass.color = 'black'
$ScottyClass._size = 'small'

One thing to note when creating an inherited class in PowerShell using this technique is that you need to recreate your constructors. This is probably the only unfortunate part of the pseudo-class inheritance that PowerShell offers. It basically forces override whether you want it or not on the constructor.

function New-Scotty {
    param(
          [Parameter(Mandatory=$true)]
          [String]$name
    )
    $dog = $ScottyClass.psobject.copy()
    $dog.name = $name
    $dog
}

Now we can use our new class:

$George = New-Scotty George

Overriding Methods

In the case of the Scotty class where we want to add a new method for catching rats, there’s nothing special that needs to be done. New methods are treated the same as on the base class. However, if you want to override a method that exists on the parent class, the technique is identical, but you’ll need to supply the force parameter to Add-Member. For example, if we want to override the Pee() method for the dog, we could do the following.

$ScottyClass|Add-Member -Force -MemberType ScriptMethod -Name "Pee" -Value {
    "The Scotty sniffs the tree.  A warm refreshing pee trickles out of {0}" -f $this.name
}

Note about PIA 2.0

Bruce Payette has a great set of code in PowerShell in Action 2.0 about extending PowerShell to wrap the OO-ability of the language in something that looks cleaner. The end result is you can define a class like this:

DogClass {
    property name 
    property size
    method Pee { "Oh yeah"}
}
$dog = new DogClass

While doing research for this post, I realized that I had never noticed this section of the book. We really need to make this a publicly available extension. It’s pretty simple to do, but I don’t want to paste his code due to copyright reasons. Actually, if you’re the kind of person who has been looking for OO in PowerShell and have never read Bruce’s book, you probably should because it will answer a whole lot more about PowerShell than just this for you.

Objects from New-Module

The PowerShell team added a shortcut way to do all of the above. Modules exist in their own closure. Because of this, they may contain their own scoped variables that can be stored within the module itself. There is a parameter on New-Module called -AsCustomObject that will convert your defined scriptblock module into a PowerShell object with note properties and script methods. Here’s an example of the Dog class using this technique.

function New-Dog {
    param(
        [Parameter(Mandatory=$true)]
        [string]$Name,
        [Parameter(Mandatory=$false)]
        [ValidateSet('small', 'medium','large', $null)]
        [string]$size,
        [Parameter(Mandatory=$false)]
        [string]$color
    )
    New-Module -Argumentlist @($name,$size,$color) -AsCustomObject {
        param(
            [Parameter(Mandatory=$true)]
            [string]$Name,
            [Parameter(Mandatory=$false)]
            [ValidateSet('small', 'medium','large', $null)]
            [string]$size,
            [Parameter(Mandatory=$false)]
            [string]$color
        )
        function Pee {
            "A warm refreshing pee trickles out of {0}" -f $Name
        }
        Export-ModuleMember -Function Pee -Variable Name, Size, Color
    }
}

If we look at the members that this creates, you’ll see it looks very similar to what we’ve been doing up until now.

$Lucy = New-Dog -Name Lucy
$Lucy |Get-Member

   TypeName: System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
color       NoteProperty System.String color=
Name        NoteProperty System.String Name=blah
size        NoteProperty System.String size=
Pee         ScriptMethod System.Object Pee();

The PowerShell Way

So what we’ve been looking at is modern object oriented programming. However, it’s rarely seen in the wild with PowerShell even though it’s very simple to implement. Why is that exactly? Sure, there are plenty of beginners out there who weren’t even developers until now, but I believe it’s deeper than that. I personally know that this exists. However, I’ve only used it on one project.

The reality is that PowerShell lives and breaths objects through everything. Also, these objects are very flexible because they can be extended or modified on the fly. Functions can take objects as input and the type of Input provided by strongly typed objects can more easily be achieved with strongly type parameters. I see the param() block of a function as something that describes the properties of an object that I expect to see.

Now this is PowerShell to me. This is a paradigm shift in the way we do object-oriented programming. To me, when my peers ask me why I’m so PowerShell crazy, this is the answer. It looks like it’s functional, but it is far from that.

function New-Dog {
    param(
        [Parameter(Mandatory=$true)]
        [string]$Name,
        [Parameter(Mandatory=$false)]
        [ValidateSet('small', 'medium','large', $null)]
        [string]$size,
        [Parameter(Mandatory=$false)]
        [string]$color
    )
    New-Object psobject -property @{
        Name = $Name
        Size = $Size
        Color = $color
    }
}

function Invoke-Pee {
    param(
        [Parameter(Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)]
        [String]$Name
    )
    PROCESS {
        "A warm refreshing pee trickles out of {0}" -f $Name
    }
}

New-Dog -Name Lucy |Invoke-Pee
A warm refreshing pee trickles out of Lucy

Not only do I have a dog that can pee, but I also can use the same Invoke-Pee function for anything that has a name property. It can also invoke-pee on a number of dogs without having to create a loop.  If I really needed to be sure it was a dog, I could take the entire psobject as a parameter and validate that it had the properties that make it a dog.  Perhaps even write the Test-Dog function to do that.

Finally, I would personally wrap the above in a module named Dog and use a prefix more like New-Dog and Invoke-DogPee. Then it’s easy to import the module and inspect what methods are available using Get-Command -Module Dog. Again, this looks and smells like it’s functional, but it’s all objects and encapsulation, and it’s amazing!

PowerChumby 1.0 Released

After doing some cleanup one evening I found my Chumby sitting between my nightstand and my heater. It had been long neglected due to the unstable wireless in my bedroom. I decided it was high time I moved it next to my computer and workspace to assist me in my daily tasks. I found it was exactly what I needed: A pandora player, a facebook/twitter feed reader, a clock, and – well you know how it goes with a chumby.

The one thing I found to be troublesome was the speed of the control panel. While on a call for work I need the ability to quickly mute the volume or dim the lights. On occasion I’d like to see a specific widget without having to push the face of the Chumby with my fat fingers.

After some digging into the Chumby twiki I decided to dust off my Perl skills in order to create an interface to many of the items found here.

The next step made perfect sense to me: Create the PowerShell cmdlets to interface with the .cgi script.  This gives me lightning fast access to the functions of my chumby from the comfort of the environment I spend all my time in.

You can download the cgi and module from codeplex.

Here’s a little taste of the things you can do with it:

$chumby = Get-chumby 192.168.1.106 # You can also load this in your profile

#play the PowerShell 2.0 Release Party Podcast
$chumby |Start-ChumbyMusic http://media.libsyn.com/media/powerscripting/PSPodcast-089.mp3 

#Mute your chumby - be quiet - there are ninjas afoot!
$chumby |Set-ChumbyMute 

#All clear - unmute
$chumby |Set-ChumbyMute -Off 

#Set the volume a bit more reasonable so Hal does not wake your neighbors
$chumby |Set-ChumbyVolume 50 

#View the next widget in your channel
$chumby |Set-ChumbyWidget -Next 

#Switch to the channel you have called News
$chumby |Set-ChumbyChannel News

#Jump to the Twitter widget
$chumby |Set-ChumbyWidget Twitter

#Stop the Powerscripting podcast from playing
$chumby |Stop-ChumbyMusic

#Time for bed - set to night mode and watch it turn into a clock again
$chumby |Set-ChumbyNightMode

#Oh, and finally for the not-so-weak
$chumby |Enable-ChumbySSH

Hey Scripting Guy! Articles Published

While it’s probably a safe bet that if you follow my site you also follow Hey Scripting Guy!  I can’t help myself from taking the opportunity to place a highlighter over my work.  I have had two articles published by Ed Wilson this past week on his site.  If you didn’t get a chance to read them I’ll be happy to point you directly to them:

The first one is regarding how to get involved in the PowerShell community.  It gives some tips and advice from someone who successfully wedged himself into the fun in a very short amount of time.  If I can do it, so can you, and without three payments of 29.99 or the cost of shipping and handling:

Tap into the PowerShell Community for Fun and Education

The second article was going to be part 3 in my series for developing a corporate module repository, but I decided it could use a bigger audience so I handed it over with a grin.  It discusses the technique I used to convert 3rd party snapins into modules using psd1 files:

Learn How to Load and Use PowerShell Snap-ins


Corporate Powershell Module Repository – Part 2 – Developer Guide

This article continues the last one about architecting a corporate module repository.

Developer Guide

I put a formal submission process in place to ensure that all modules meet certain standards.  Developers are given a complete set of documentation that outlines best practices that are enforced via a review process.  When a developer submits a module for distribution in the repository their module is checked to ensure that it meets the following guidelines:

Namespaces

  • All in-house modules must “make sense” and must have the company-specific prefix.  Let’s say that prefix is Toe.  Something like ToeISE or ToeClipboard would be ok.
  • When a namespace already exists that is suitable for a developer’s module they should first try and contact the creator of the namespace to ensure there are no duplicate efforts.  Collaboration on modules is encouraged.
  • The noun of a cmdlet must use the modulename as a prefix or the noun can be the prefix itself, i.e., if the module is named ToeISE a proper cmdlet name might be Open-ToeISE, or if the module is called ToeHTML a proper cmdlet name could be ConvertTo-ToeHTMLTable
  • Cmdlets must use a valid verb returned from Get-Verb, and that verb should follow suit with what Microsoft currently uses the verb for.  For example, Out-ToeExcel would be more appropriate than Write-ToeExcel because the intention of “Write” is to write information to the user through the console.
  • When using add-type in your module you should ensure that the -namespace parameter uses your module name.  A great example of this is a module that wraps pinvoke code.  The following might be a part of a module called ToeClipboard:
$sig = @"
[DllImport("user32.dll")]
public static extern IntPtr GetClipboardData(uint uFormat);
[DllImport("user32.dll")]
public static extern uint EnumClipboardFormats(uint format);
[DllImport("user32.dll")]
public static extern bool OpenClipboard(IntPtr hWndNewOwner);
[DllImport("user32.dll")]
public static extern bool CloseClipboard();
[DllImport("user32.dll")]
public static extern bool EmptyClipboard();
[DllImport("user32.dll")]
"@
Add-Type -MemberDefinition $sig -Namespace ToeClipboard -Name User32

Module Files

  • Developers should create modules using .psm1 files where possible
  • Strict guidelines are given around .psd1 files.  There must be a .psd1 file with every distribution.  I have provided our developers with a company-specific script that runs New-ModuleManifest to ensure that fields are filled out appropriately.

Parameters

  • Parameters must be defined with param(), and they must contain the appropriate Parameter attributes outlined in Get-Help about_Functions_Advanced_Parameters

Help

  • All cmdlets must include inline help.  The following template was supplied to developers:
<#
 .Notes
 NAME:
 AUTHOR:
 Version:
 CREATED: 6/17/2010
 LASTEDIT:
 6/17/2010 1.0
 Initial Release

 .Synopsis
 One line blurb that discusses cmdlet

 .Description
 Multi-line description of cmdlet

 .Parameter ParameterName1
 Description of ParameterName1

 .Parameter ParameterName2
 Description of ParameterName2

 .Inputs
 Information about the types of input objects accepted

 .Outputs
 Information about the objects returned as output from the cmdlet

 .Example
 Detailed example.  Ideally there should be an example for each set of Parameters and a pipeline example.

 .Example
 Additional example

 .LINK
 Related cmdlets or relevant URL

#>

Aliases

  • Aliases for cmdlets/functions should not be exported.  This is required to ensure that there is no contention and clobbering of aliases.  Aliases may be suggested in your notes, but they must not be forced.
  • Aliases for parameters are OK because they are contained within the cmdlet.  They may also be necessary to ensure that pipeline input by name is accepted from multiple sources.

Internet Repository

Are you surprised that I would end this article calling for a Powershell module repository on the Internet? While the infrastructure and design of the in-house module repository I put together for my company will not work on the Internet, I think the standards I outlined for the developer’s guide is something that is enforceable for the module repository we will build on the Internet. In order to be successful we will need a governing body that ensures namespaces and standards are met with new code. I feel that this is the easy part. Just look at all of the Judges for the 2010 Scripting Games; Reading 100 scripts a day that are all nearly identical is hardly anyone’s idea of fun yet there were plenty of community leaders who were happy to take up the task when asked. I think the hard part is the design and maintenance of the infrastructure on the Internet. So get to work people – I guarantee you we can assemble the board to oversee it justly, fairly, and with proper standards in place.

Part three of this series will include my techniques for migrating snapins to modules for both in-house and 3rd party snapins.

Corporate Powershell Module Repository – Part 1 – Design and Infrastructure

Modules – Modules – Modules

If you’ve ever had a Powershell conversation with me the chances are high that you’ve heard my agenda around modules.  I think that the 2.0 module cmdlets were exactly what Powershell needed.  I come from the land of Perl where modules are an integral part of the community.  Well-formed reusable code is something Perl developers take a lot of pride in.  Unfortunately, the Powershell community has been very delinquent in picking up the Microsoft implementation of modules.  This is mainly due to the lack of a well-designed Internet repository that users can easily install and import from.  While this is a difficult task that we have yet to take on in the Powershell community, it is a much easier task to take on in a corporate environment.

Unfortunately, I can’t disclose the name of the company I work for, but I can tell you it is very large with about 100,000 users and plenty of IT folks to support it.  When I started there three months ago I was extremely surprised at how amazing their Perl repository and documentation was complete with wiki sites, in-house modules, and distribution of those modules through a globally distributed file system.  This was a fountain of information to drink from.  Then I went to the Powershell site: ugh – a well of cyanide!  The home page still referred to it as Monad!  The snapins that were installed to the distributed file system were versions behind, and the documentation really needed an overhaul that reflected the usage of both Powershell 1.0 and 2.0 together.

It was a daunting task, but I quickly volunteered to take it on.  Sweating nights and weekends I not only redesigned the documentation, but I put together a module repository that I am extremely proud of.  Here’s what I did:

Gathered Requirements

I first spoke with users of Powershell in the company to understand how it’s used, and to understand what ways a module repository could be helpful.  I came up with the following requirements:

  1. The repository had to support multiple versions of the same module
  2. Users who were using Powershell as a shell expected to have the most recent version of a module loaded when using Import-Module
  3. Users who were writing scripts needed the ability to specify a version number of the module to use in order to ensure that a new version of a module does not break their script.
  4. The repository needed to be available globally by all workstations and servers
  5. The repository required robust documentation for each module.

I had one additional requirement that came from me:  Use the built-in cmdlets for modules i.e. Import-Module and Get-Module

Leveraged Existing Infrastructure

The distributed file system they use is extremely custom and is AFS based.  While my design could use any CIFS share it was nice to have something with such strict control.  I can publish to a \dev workspace for testing.  Users only have access to DEV if they have their machines configured to use it via the registry.  After testing and development is complete I can lock the DEV instance of a module and publish the files to a read-only file system accessible by all users and servers in the company around the world.  The read-only piece is key.  If you wind up configuring your in-house module repository using my method I highly recommend making the repository read-only with only a few key accounts having access to publish.

For the remainder of this article we will call the published read-only share: \\powertoe\modulerepo\

Configure the Code Access Security Policy

This is the only piece that requires administrator privileges.  Because the distributed file system we use is already leveraged heavily by other .NET groups within the company this part was already in place in our desktop and server builds.  In order to allow .NET to trust a share so it can load .DLLs from it you need to modify the policy with caspol.exe.  Caspol.exe can be found in your .NET directory, and documentation about it’s usage can be viewed here.

Here’s a quick batch script that will determine the proper directory to run caspol.exe from, and then call caspol.exe with the proper parameters to configure our system to trust the \\powertoe\modulerepo share:

@echo off
setlocal
for /f %%a in ('dir %windir%\Microsoft.Net\Framework\v* /b') do @call :caspol %windir%\Microsoft.Net\Framework %%a
for /f %%a in ('dir %windir%\Microsoft.NET\Framework64\v* /b') do @call :caspol %windir%\Microsoft.Net\Framework64 %%a
endlocal
goto :EOF
:caspol
set sc_dotnetpath=%1
set sc_dotnetversion=%2
%sc_dotnetpath%\%sc_dotnetversion%\caspol -polchgprompt off
%sc_dotnetpath%\%sc_dotnetversion%\caspol -m -ag 1 -URL "file://///powertoe/modulerepo/*" FullTrust
%sc_dotnetpath%\%sc_dotnetversion%\caspol -polchgprompt on
goto :EOF

This step is only necessary if you are either using snapins that you will convert to modules or if your modules load dlls of any kind.

Design the Folder Structure

This was a big hurdle to overcome.  Because I needed to nail the versioning of modules for many different purposes I spent a lot of time toying with using the -version parameter of Import-Module.  What a dead end that was!  The -version parameter seems to have been an afterthought to versions: Documentation that doesn’t work and a backwards method of selecting the highest version of a module frustrated me to no end.  You can read a little more of this digression here.

After throwing away “Import-Module -version” I came up with an elegant solution to the problem.  I decided I could script the user’s $env:PSModulePath.  If you are not aware of how modules become available to the built-in cmdlets Import-Module and Get-Module you should read:

Get-Help about_modules

To summarize the relevant parts: the environment variable PSModulePath holds the file paths that the built-in cmdlets look in to find modules that are available.  For example if the PSModulepath is set to c:\modules and you have a directory in that path called ToeISE with a ToeISE.psm1 or ToeISE.psd1 file it will be an available module when you run:

Get-Module -ListAvailable

If it is in that list you can then load the module with the following:

Import-Module modulename

As I mentioned I did struggle with trying to use a folder structure like \\powertoe\modulerepo\modulename\version or even \\powertoe\modulerepo\modulename\version\modulename with the $env:PSModulePath pointing to \\powertoe\modulerepo, but nothing worked quite the way I needed it to.  In the end I used the following configuration:

\\powertoe\modulerepo\modulename\version\modulename

Design the Profile

To fix the PSModulePath to point to the latest version of every module in the repository I added the path of the latest module to $env:PSModulePath through a quick one-line script:

Get-ChildItem \\powertoe\modulerepo\|where{$_.psiscontainer}|foreach {Get-ChildItem $_.fullname|Where{$_.psiscontainer}|sort -Descending|select -First 1}|foreach {$env:PSModulePath += ";" + $_.fullname}

or for better readability:

Get-ChildItem \\powertoe\modulerepo\|where{$_.psiscontainer}|foreach {
    Get-ChildItem $_.fullname|Where{$_.psiscontainer}|sort -Descending|select -First 1
}|foreach {
    $env:PSModulePath += ";" + $_.fullname
}

In the end, however, I wrapped this one line in a script and published it to \\powertoe\modulerepo\modulerepo.ps1.  I documented that users should dot (.) source this script in their profile.  It’s a similar copy/paste operation, but it allows me to control the profile of our users if anything in the module repository ever needed to change.

. \\powertoe\modulerepo\modulerepo.ps1

There is one small problem with my method:  It may not scale very well.  As the module repository grows it is possible that the script that loads PSModulePath will be too cumbersome.  If that ever happens I can modify modulerepo.ps1 to include the full path for each module.  It would then be a part of the controlled distribution process of any module to update this script with the new version of the module uploaded.  Rather than introducing a manual point of failure I decided to tackle this problem if the module repository ever grew large enough where it would impact performance.  There is also the question of whether performance will ever be hindered by a PSModulePath that becomes too large.  Again, a problem I am resolved to tackling down the road if it ever manifests.  In the meantime, the above solution gives our users a nice way to use get-module -listavailable and import-module modulename to use the central repository.  This works for the latest version of any module, but it doesn’t handle how to specify a version to use.

Controlling Versions in Scripts

The solution is really a no-brainer.  Import-module supports the usage of paths with the -name parameter.  So simply users are instructed to use the full path to the .psd1 in their scripts:

Import-Module -name \\powertoe\modulerepo\PowerCLI\4.1.0\PowerCLI.psd1

Because we control what gets distributed to the module repository we can enforce standards e.g. Must have a psd1 file, must have inline help with specific fields filled out, must use proper namespaces, etc.  Perhaps I will share that bit of information at a later date with you, but I know that there’s something that will interest you much more than an article about best practices.  If you read the above example for Import-Module you’ll see that I’m using it on a .psd1 file for PowerCLI – Wait a minute – Does that exist?  Does that work?  Can anyone guess what I will writing about in a future article?

Recap

  • Create share and publish modules to \\server\share\modulename\version\modulename
  • run caspol to trust \\server\share
  • Use the one-liner in your $profile:
Get-ChildItem \\server\share\|where{$_.psiscontainer}|foreach {Get-ChildItem $_.fullname|Where{$_.psiscontainer}|sort -Descending|select -First 1}|foreach {$env:PSModulePath += ";" + $_.fullname}
  • Use the following to view available modules:
Get-Module -listavailable
  • Use the following to import modules from the repository:
Import-Module modulename
or
Import-Module -name \\server\share\modulename\version\modulename\modulename.psd1

Modules – Modules – Modules

They are a cool powerful way of sharing your well-formed code.  It is a great way to promote best-practices internally and enforce your policy on reusable code.  They are also an amazing utility that lets you quickly browse and install other people’s code.  I strongly suggest you start using them.  Next stop, let’s do this on the Internet people – a real CPAN-like module repository!

In part 2 we will look at the developer guidelines I created for in-house modules in order to make them suitable for publishing in the repository.

Open a file in Powershell ISE via cmdlet

Today I found myself annoyed at the process I have to go through to open a script while working in ISE.   I decided to put together a quick cmdlet that will make this very easy.  I used the technique from my last post to ensure that I could pipe in a collection of scripts to open at once.  Perhaps I’ll grow this module with time, but for now you can download it with the single cmdlet here (Make sure to unblock the .zip prior to extracting).  I’ve also uploaded the code behind the function to Poshcode.

Here is the contents of get-help open-ISEFile -full:

NAME
    Open-ISEFile

SYNOPSIS
    Open a new file in ISE

SYNTAX
    Open-ISEFile [-Path]  [-PassThru] []

DESCRIPTION
    This cmdlet allows you to open a file in a new file tab within your current Powershell t
    ab.  You can pass a collection of files to open more than one file.

PARAMETERS
    -Path 
        Specifies a path to one or more files.  Wildcards are permitted.  The default locati
        on is the current directory (.).

        Required?                    true
        Position?                    1
        Default value
        Accept pipeline input?       true (ByValue)
        Accept wildcard characters?  

    -PassThru []

        Required?                    false
        Position?                    named
        Default value
        Accept pipeline input?       false
        Accept wildcard characters?  

        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer and OutVariable. For more information, type,
        "get-help about_commonparameters".

INPUTS
    System.String

OUTPUTS
    None or Microsoft.PowerShell.Host.ISE.ISEFile

    When you use the PassThru parameter, Open-ISEFile returns a Microsoft.PowerShell.Host.IS
    E.ISEFile for each file opened.  Otherwise, this cmdlet does not generate any output.

NOTES

        Name: Open-ISEFile
        Author: Tome Tanasovski
        Created: 6/20/2010
        Version: 1.0

    -------------------------- EXAMPLE 1 --------------------------

    C:\PS>Open-ISEFile -Path $profile

    Opens your profile in ISE.

    -------------------------- EXAMPLE 2 --------------------------

    C:\PS>dir *.ps1 |Open-ISEFile

    Opens up all ps1 files in the current directory as new file tabs in ISE.

    -------------------------- EXAMPLE 3 --------------------------

    C:\PS>Open-ISEFile *.ps1

    Opens up all ps1 files in the current directory as new file tabs in ISE.

    -------------------------- EXAMPLE 4 --------------------------

    C:\PS>$file = Open-ISEFile "c:\file1.ps1" -PassThru

    Opens up file1.ps1 in ISE.  The command uses the passthru parameter to generate an objec
    t that represents a file in ISE.

RELATED LINKS
     https://powertoe.wordpress.com
%d bloggers like this: