PowerShell Version 2

When Import-Module Does Not

A question came up about the behavior of Import-Module (in the context of this interesting discussion of whether module authors should provide aliases), especially with Version 3 and the auto-loading of modules (what’s that?).

So What’s the Question?

Does the auto-loading of modules in PowerShell Version 3 use Import-Module behind the scenes?  If it does, can we use default parameter sets to control what types of things are imported?

What Actually Happens?

So, it turns out that Import-Module does get called behind the scenes and a default parameter set (new feature) can be used to customize the import of new modules.

$PSDefaultParameterValues = @{
	"Import-Module:Alias"=@()
	"Import-Module:Function"='*'
	"Import-Module:Cmdlet"='*'
	"Import-Module:Variable"='*'
}
Get-Module
Get-BitsTransfer
Get-Module

This example (when used in PowerShell V3) will (from a base PowerShell session) show you what modules are loaded, attempt to run the Get-BitsTransfer command, and then show you what modules are now loaded (should include the BitsTransfer module now).  Since we’ve provided an empty array to the Alias parameter for Import-Module, any module that is imported will import any aliases.

Seems Like It Works… Where’s the Problem?

The problem comes in when you specify that only one of the four parameters (Alias, Function, Cmdlet, and Variable).

You might expect that when you specify one parameter (example Alias), that the other portions of the import (Function, Cmdlet, and Variable) would behave just like if you did not specify any of them.

Example -

Open a new PowerShell session (V2 or V3) -


Import-Module BitsTransfer
Get-Command -Module BitsTransfer

Get-Module

This will import BitsTransfer module and all attendant commands and functions.  Note the ExportedCommands in the Get-Module output.

Open a new PowerShell session and try -


Import-Module BitsTransfer -Alias @()
Get-Command -Module BitsTransfer

Get-Module

This should import the BitsTransfer without any aliases (yes, I know BitsTransfer doesn’t have any aliases, but I wanted a module that most any Windows 7 or Server 2008 R2 machine would have – feel free to test with whatever module you desire). 

This did import the BitsTransfer, but no commands or variables were imported.  It only attempted to import aliases, and since we specified that aliases were to be an empty array, nothing was imported.

This is counter-intuitive and is not the experience we should have.  Specifying one parameter should not change the default value of other parameters (from all to none in this case).

What Can I Do About It?

First of all, open PowerShell and try it!  If you have one of the CTPs of Version 3, try it there.  Also try it in V2 using Import-Module directly.

Then go to Microsoft Connect and vote up https://connect.microsoft.com/PowerShell/feedback/details/716857/module-partially-loads-with-import-module.

Checking Your Pipes For Leaks

(NOTE: I started this post a few weeks ago)

This morning I woke up with my kitchen floor covered in water.  As I was cleaning it up, I couldn’t help drawing some parallels between fixing a leaky pipe and a "leaky" (non-performing) script.

My repair process this morning took the following steps:

  1. Contain the leak
  2. Locate the source of the leak
  3. Stop the leak
  4. Check for related damage
  5. Mitigate the related damage
  6. Patch the leak 
  7. Confirm the leak is fixed
  8. Monitor the leak site

Since most of you won’t really care about the plumbing in my kitchen, let’s run through how dealing with this problem applies to troubleshooting a misbehaving script (or function).

Containing the Leak

The first step I needed to take was to contain the leak.. in PowerShell, that means I need to pull that script or function from general use, as well as terminating any running instances.

This can be tougher than you think, especially if you are using remote jobs or are performing a sequential series of actions that cannot be rolled back (especially if you don’t have good logging in place!).

Locating the Source

After I’ve got the leak contained, I had to find where the leak was coming from.  For my misbehaving script, I either need to find what has failed.  Normally, the errors that I’m getting will help point me towards the source of the problem. 

However, if I’m not getting errors, I need to step through the script (or function) and can use Set-PSBreakpoint to create an entry point in my script where I want to start walking through. 

If my script (or function) is an advanced function, I could use -Debug and have it break on any Write-Debug statements that I’ve used throughout my script.  This is a GREAT reason to have a periodic Write-Debug statement before any significant blocks of logic in your script.

Stopping the Leak

Once I’ve found the source of the leak, I can begin to stop the leak.  In my kitchen leak, I had to turn off the water.

In the case of my script, I have to find any scheduled tasks using it and stop them or advise other people who might be using the script to stop using it until I can publish a new script.

Checking for Damage

Once I had my leak stopped, I started to check for residual damage that I might have to control.  Water had leaked onto where the power cord for my water heater plugged into an extension cord,  This caused the GFI outlet to trip.

Collateral damage from a misbehaving script or function can be disastrous.  Very often, scripts are run under administrative credentials and can have unexpected side effects.  When locating the source of the leak, I’ll check to see what other systems or areas that the script is touching and examine them for potentially improper changes or updates.  Finally, I need to document any “damage” or errors that the script created.

Mitigating the Damage

Once I found that the GFI was tripped, I unplugged everything on that chain.  I let everything dry out and replaced the extension cord.

For a misbehaving script, this is going to vary based on what the script was actually doing, but this can be one of the most important steps.  I have to record all the actions I take to mitigate the “damage” found in the previous step.  Even if I decide not to do anything about the damage I found in the previous step, that is still an action and should be documented.

Patching the Leak

Patching the leak is pretty straightforward.  Going to the source of the leak, I removed the faulty tap and patched the pipe.

In my script, that means going to the source of the faulty logic or source of the errors and either fixing the logic and/or adding error handling to deal with the source of the errors.  I’ll also add additional Write-Verbose or Write-Debug statements to make tracing problems and verifying functionality easier.  For scripts that will

Confirming the Leak is Fixed

After I patched the leak, I turned the water back on and watched it for a short while to make sure that there was no seepage from the patch.  I tested down-level plumbing to make sure that the pressure was appropriate as well.

After I’ve added my error handling and/or fixed the logic, I’ll run the script again, perhaps with –Verbose or –Debug.  If I’m really motivated, I’ll make sure there is the CmdletBinding attribute and add –WhatIf support.

Monitoring the Leak

After I was able to confirm that the leak was fixed, I stayed home and periodically checked the line to watch for any seepage or residual problems.  Over the next few days, I continued to monitor the site of the patch for any issues, though not as frequently.

After I’ve fixed my script, I’ll continue to watch was it gets used, perhaps logging all the output, until I’ve regained confidence in the project.

Resources for the WI .NET User Group

I’ll get the slides posted sometime (in the middle of migrating my machine to the developer preview of Win 8), but the links I mentioned are:

Project Home – http://www.show-ui.com

Codeplex Site – http://showui.codeplex.com

Doug Finke’s Blog – http://dougfinke.com/blog

Joel “JaykulBennet’s Blog – http://huddledmasses.org

James Brundage’s Blog – http://blog.start-automating.com/

Waiter, There’s a Bug in My Get-Help!

My friend Robert Robelo was kind enough to point out a problem with the proxies created by the CommandAssist module.

In trying to research the problem, I found that PowerShell crashed ( yes, crashed.. not throwing an error, but CRASHED) when Get-Help could not resolve the command it needed to provide help for.

Setting the Stage

Reproducing the Problem

Create a module that exports a function

$Path = 'c:\users\smurawski\documents\windowspowershell\TestModule.psm1'
$null = new-item $Path -ItemType File -Force -Value @'
function Test-One {
	[CmdletBinding()]
	param($x)
	$x
}

Export-ModuleMember -Function Test-One
'@

Import the module

Import-Module TestModule

Create the proxy module

Get-Module TestModule |
	New-AssistedModule -ModulePath c:\users\smurawski\documents\windowspowershell\TestAssisted

Import the proxy module

Import-Module TestAssisted

Try to Get-Help for Test-One (shell will crash here)

Get-Help test-one

What’s Happening?

In the proxy command that is generated, there is a help item (ForwardHelpTargetName) that is created to point the help file back to the original command.  Since you could possibly have multiple commands, aliases, and functions with the same name, there is a second help entry (ForwardHelpCategory) that allows you to specify what type of command you are forwarding to.

NOTE:  The following is my opinion of what is happening based on my testing.  I haven’t attached a debugger and watched to verify, but I have tested enough to confirm a workaround.  This occurs in both the ISE and PowerShell.exe.  I have not tested in other scripting environments.

In this case, both the original command and the proxy are functions, so Get-Help goes into a loop until it hits a recursion max and crashes PowerShell. 

Get the Duct Tape

Finding a Workaround

So, how can we get around this?  CommandAssist relies on pointing back to the source command for the help files. 

Fully qualifying the command in the proxy will solve the problem.  I’ve updated the proxy command generation to qualify the command in the ForwardHelpTargetName with the module prefixed name of the source command.

The Downside

If you’ve been playing with CommandAssist, you will want to regenerate your proxies.

Let’s Get This Fixed

Vote it up on Connect!

I don’t know about you, but I would like to see this type of error not crash PowerShell.  I’ve filed a Connect bug and would appreciate if you would head over and vote it up!  The team really does respond to community feedback and Connect is a great way to show that this is important to you!