Dienstag, 30. März 2010

PowerShell modules are funny guys (II)

Here I'll focus on the irritating feature. The following script creates 2 modules in your user modul folder.
The second module imports the first module.

Import the first module, then the second. Both modules are imported.
Now remove the second module (that which imports the other too) and both are gone.

$modpath1 = ($env:PSModulepath -split ';')[0]            
mkdir $modpath1\submod -force
mkdir $modpath1\testmod -force



@'
$script:state_s = 0

function Get-State
{
return $script:state_s
}

function Set-State ($x)
{
$script:state_s = ($x)
}
'@
> "$modpath1\submod\submod.psm1"


@'
Import-Module submod.psm1


function Get-StateY
{
return Get-State
}

function Set-StateY ($x)
{
Set-State $x
}
'@
> "$modpath1\testmod\testmod.psm1"

ipmo submod
ipmo testmod
Write-host '------------'
gmo
Write-host '------------'
rmo testmod
gmo


Do you agree, that is to be called a bug?

Bernd

Montag, 29. März 2010

How to Organize Modules you Develop in Projects

Today I want to tell about the ways I organise my modul development.

When you start to develop PowerShell modules it is relative easy. Just add a folder under
(Split-path $profile) + '\Modules'            

say MyMod.
Then put a file MyMod.psm1 into it

Write-host "mymod loaded"


When you type ipmo Mymod, which is the alias for Import-Module you get


mymod loaded


With rmo Mymod , which is the short for Remove-Module you get rid of your module.

To check that it is removed, you can type
gmo or gmo Mymod, which is the alias for Get-Module

Now you shouldn't see it anymore.

Here I'm not going to tell you, what to put into your module file.

Best link for this I know is this post from Bruce Payette.

My focus here is, how to organise your system, when you have written something usefull and uploaded your module to some project.

I guess that you checkout your module from the project to some place, which is not in your modules folders. To ckeck where your system searches for modules just type

$env:PSModulePath -split ';'


Next I tried to be clever. I put the following into my profile:

$env:PSModulePath += ";\D:\MysharedProject\modules"


Now I can load the modules from this project by simple calles of ipmo myModule.

It really took some time to discover that I was too clever.

I'm using ISE for nearly all my PowerShell task and some time ago I discovered the menu New PowerShell Tab with the short-cut CTRL+T.

Each time you invoke this, you get a new PowerShell session and your PowerShell Profiles are executed.

But all these PowerShell session live in your current Windows session and each time the PSModulePath variable of the Windows environment will be modified. It gets longer and longer.

OK here now my final solution, this is the start of my profile.ps1

write-host "================== generall ($env:USERDOMAIN.$env:username) ==========================="            

$AdditionalModulPathes = @(
"D:\someProjectBleedingEdge\Modules"
"D:\someProjectRelease\Modules"
# "D:\MyCopyOfSomeProject\Modules"
)


foreach ($path in ($AdditionalModulPathes| Get-unique) )
{
if (($env:PSModulePath -split ';') -notcontains $path)
{
$env:PSModulePath += ";" + $path
}
}

# $env:PSModulePath -split ';'



Now I modify the evironment only once.
And here is the location, where I select whether I use the stable release, the current development version or my own unpublished work.

I hope this give you some ideas.

Bernd

BTW: ISE-Cream has reached release 0.1
If you are thinking about extending ISE, take a look at it

Donnerstag, 25. März 2010

PowerShell modules are funny guys

Ed Wilson says Windows PowerShell 1.0 was the vision, Windows PowerShell 2.0 is the reality [ from the preface of LeeHolmes Windows PowerShell Cookbook
I dare say PowerShell 2.0 ISE and Modules are still a vision.

When a module depends on a another module like SQLISE on ICECreamBasic or WPK it seems natural
to use within the .psm1 file
import-module ISECreamBasic            
import-module SQLParser
import-module adolib
import-module WPK


When I write a second module OracleIse, I can do similar

import-module ISECreamBasic            
import-module OracleClient
import-module WPK


Seems OK so far.

Next modules can be removed using Remove-module and that fires even an event to do some clean up as removing ISE addons menu items

$mInfo = $MyInvocation.MyCommand.ScriptBlock.Module            
$mInfo.OnRemove = {
Write-Host "$($MyInvocation.MyCommand.ScriptBlock.Module.name) removed on $(Get-Date)"
Remove-IseMenu OracleIse
}


Now emove on of these modules an see, which modules are left using

get-module            


You see, that the removed modul removed the modules imported within too, without tribute to the fact that the other module depends on WPK and ISECreamBasic too.

Conclusion. Better do not use import-module to import module from within your modules. Import the needed modules in advance.

Have some fun trying to find work arounds for this issue.

Bernd

Removing a specific Item from a Powershell ISE Addon menu

Well in ISECreamBasic I had written the function Remove-IseMenu to remove Toplevel menus.
Thanks to Shay Levi and Jaykul it became a nice advanced function with parametersets.

You can find the code in the ISE-Cream project

Just when I finished writing the header comments Ravi wanted to use the function not restricted to toplevel menu items.

First attemps tried to search the tree, but there can be more subitems with the same name hidden in the menu structure. To get it clear I designed the following function, which requires the complete path to the item to delete:

function Remove-IseMenuItem {            

$submenu = $psise.CurrentPowerShellTab.AddOnsMenu.Submenus

for ($j = 0; $j -lt $args.count; $j++)
{
$name = $args[$j]
$count = $submenu.count
#Write-host "arg0: $name $count"
for ($i = 0; $i -lt $submenu.count; $i++)
{
$found = $false
if ($Submenu[$i].DisplayName -eq $name)
{
#Write-host "found toplevel menu $name"
$found = $True
if ( $j -eq ($args.count - 1))
{
Write-host 'Removing'
$null = $submenu.remove($Submenu[$i])
}
else
{
$submenu = $Submenu[$i].submenus
}
break
}
}
if (! $found)
{
Write-host "Menuitem not found"
break
}

}
}

Remove-IseMenuItem Edit Pad

# Remove-Child $Submenu[$i] $Name | out-null



But this looks like an old PowerShell V1 coding style. It doesn't check that all arguments are strings. Please can someone show me, how to set up a PowerShell V2 advanced function parameter declaration for this.

Sonntag, 14. März 2010

ISE and PowerShell Modules

The last days searched a bit the current links about PowerShell modules.

There are the two new links by
Bruce Payette

http://dotnetslackers.com/articles/net/Converting-a-PowerShell-Script-into-a-Module.aspx
http://dotnetslackers.com/articles/net/Converting-a-PowerShell-Script-into-a-Module-part2.aspx

and in PowerShellPack there are lots of examples.

When you have a script module, that is one with a .psm1 file, it is easy to create a personal variant of it, be just copying it to a new folder within PSmodulePath and rename its
.psm1 file to correspond to the new foldername.

There might be .psd1 and .ps1xml files which require similar and further adaptions. The modules I'm working on don't have them.

But being modules that extend ISE, they use the addons menu and the current defacto standard to build such menus is the use of the Add-IseMenu from ISEpack.

The original version from James Brundage has some shortcomings, but I succeeded in writing a compatible extension, which you find in ISE-Cream. The main advantage, is that it only warns when shortcut keys are used and just builds the menu ignoring them.

That makes it easy to copy a module, rename the top menu name in one of them and load them side by side.

At first the menus look good, but when you try them, they seem to use the function from the last imported module.
When 2 modules export the same function name, the name is rebound to the function from the last imported module. Usually menus call their functions just by name and do not hard weir the modulename into the call.
In the end both menus call the functions from the last imported module.

Of course you can take the risk and edit the modul into all function calls, but that is a lot of work and seems error phrone.
Ignorant to the possibility to prefix the calls with the module name, I added a module parameter to the ICE-Cream version of Add-IseMenu, so that I have to set it at a single point.

Whenever I see the following code:
import-module IsePack          

I replace it with
if (! (test-path function:Add-IseMenu ) ) {import-module IsePack }          
Did I mention, that in the ISE-Cream version, you can predefine the order of the entries?


Bernd

PS:
I noticed, that undersores in modulnames will not be displayed. Better don't use them





PauerSchell Bernd joined SQLPSX

Hello database and PowerShell folks,

some days ago Chad Miller released SQLIse and soon afterwards Steven Murawski joined the SQLPSX project.

From the SQLIse project you can learn a lot about using PowerShell modules and building GUI with WPK. SQLIse uses System.Data.SqlClient, that is Ado.NET to access databases.

In the past I did some unpublished work using System.Data.OracleClient aka ODL on the database side.
In the old CTP3 days I joined the PowerShell ISE-Cream project, which did suffer a bit by the incompatible changes, which came with the PowerShell V2 RTM version.

But motivated by the execellent postings of Ravikanth Chaganti about PowerShell remoting, we began to revive that project and I learned a lot about working with multiple tabs (= PowerShell Sessions) and tuning the addons-menu.

Now I joined SQLPSX too.

I'm going to add the Connect and Invoke-ExecuteSql functions adapted to Oracle the next days.

The interface to procedure calls will take a bit longer as the coresponding SQL-Server functions aren't yet called by menu in the current version.

And as far as I know there is nothing comparable to SMO for Oracle.

One of the features I miss most on the current Oracle tools, is the fact, that they do help to display multiline fields in a wellformatted manner. Grid views tend to show only the first line and text views loose alignment. With PowerShell's Format-List cmdlet and Steves 'Result to variable' option, we can do better.

That's for now database folks, I have to do some PowerShell basics.

Bernd

Sonntag, 7. März 2010

PowerShell and Network Drives

Hello again,
PSDrives behave something different then mapped network drives and until now I still used a cmd script to map and unmap network drives.

But I just stumbled about some posts from The scripting guys and went back to Guy's Scripting Ezine 3 - Map Network Drive
where is told how to do it in vbs.

Finally I put it all together and translated it into the modern scripting language:

            
function New-NetworkDrive
{
<#
.synopsis
A function to create Networkdrives
.Example
New-NetworkDrive 'x' '\\localhost\C$'
.Example
New-NetworkDrive 'Y:' '\\localhost\C$\Windows'
#>

Param(
[string]$Drive,
[string]$Unc
)
$net = New-Object -com WScript.Network
if ($Drive.length -eq 1) { $Drive = $Drive +':' }
"$Drive $UNC"
$net.mapnetworkdrive($Drive, $Unc)
# ToDo -- currently I don#t need it
#$net.mapnetworkdrive($Drive+ ':',$Unc, $bProfile, $User, $password)
}


function Get-NetworkDrives
{
<#
.synopsis
A function to list the currently mapped Networkdrives
.Example
Get-NetworkDrives
#>

$mappedDrives = @{}
$net = New-Object -com WScript.Network
$a = $net.EnumNetworkDrives()
$anz = $a.count()

for ($i = 0; $i -lt $anz; $i = $i + 2)
{
$drive = $a.item($i)
$path = $a.item($i+1)
$mappedDrives[$drive] = $path
}
$mappedDrives
}

function Remove-NetworkDrive($Drive)
{
<#
.synopsis
A function to remove Networkdrives
.Example
Remove-NetworkDrive X
.Example
Remove-NetworkDrive Y:
#>

$net = New-Object -com WScript.Network
if ($Drive.length -eq 1) { $Drive += ':' }
$net.removenetworkdrive($Drive)
}


# see also
# http://www.computerperformance.co.uk/Logon/Logon_MapNetworkDrive.htm
# http://www.computerperformance.co.uk/Logon/LogonScript_enumnetworkdrives.htm#EnumNetworkDrives_Syntax

# http://www.computerperformance.co.uk/powershell/index.htm
# http://blogs.technet.com/heyscriptingguy/