Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I have been reading your articles about Windows PowerShell modules this week, but I am still a bit confused about where they go, how to get them there, and the best practice for dealing with them. Do you have any insight about how to actually work with Windows PowerShell module paths?

-- KG

 

Hey, Scripting Guy! AnswerHello KG,

Microsoft Scripting Guy Ed Wilson here. I am drinking espresso this afternoon because I am answering the scripter@microsoft.com e-mail. I have a small stovetop espresso machine I picked up in Naples, Italy, many years ago that I use to brew the espresso. I was up late last night reading David Copperfield (Scripting Guys do not read only technical books), and I need an extra kick-start. I do not use any sugar in my coffee or espresso; instead, I place a cinnamon stick in the cup, a trick I picked up in Lisbon. As I sit staring blankly at my computer monitor, my mind wanders back to a similar time when I sat drinking espresso. Instead of being bundled up and freezing, however, I was wearing short sleeves and relishing a cool gentle breeze. The sky was an incredible cobalt blue, and I was at peace with the world. I snapped the following photo as I mellowed out in a sidewalk café in Buenos Aires.

Image of beautiful day in Buenos Aires

 

I was in Buenos Aires teaching a VBScript class at the Microsoft offices down there, and as I sat sipping my espresso, I was the happiest guy in the world—awesome friends, excellent coffee, fantastic weather. What more could one want?

KG, it is amazing how powerful certain memories can be, and how quickly they can be triggered. Maybe because I seldom drink coffee, I can remember nearly every cup I ever drank and the occasion for it. The cup of espresso in Buenos Aires is high on my top 10 list of favorite cups of coffee.

Enough daydreaming. Let’s get back to work. The path that will be used to store the module is stored in the $modulepath variable. This path includes the path to the users' modules folder plus a child folder that is the same name as the module itself. To create the new path, it is a best practice to use the Join-Path cmdlet instead of doing string concatenation and attempting to manually build the path to the new folder. The Join-Path cmdlet will put together a parent path and a child path to create a new path. This is seen here:

$ModulePath = Join-Path -path $userPath `
               -childpath (Get-Item -path $name).basename

To Windows PowerShell 2.0, the Windows PowerShell team added a script property called basename to the System.Io.FileInfo class. This makes it easy to retrieve the name of a file without the file extension. Before Windows PowerShell 2.0, it was common to use the split method or some other string manipulation technique to remote the extension from the file name. Use of the basename property is shown here:

PS C:\> (Get-Item -Path C:\fso\HelloWorld.psm1).basename
HelloWorld

The last step that needs to be accomplished is to create the subdirectory that will hold the module and to copy the module files into the directory. To avoid cluttering the display with the returned information from the New-Item and Copy-Item cmdlets, the results are pipelined to the Out-Null cmdlet:

New-Item -path $modulePath -itemtype directory | Out-Null
Copy-item -path $name -destination $ModulePath | Out-Null

The entry point to the Copy-Modules.ps1 script calls the Test-ModulePath function to determine if the users' modules folder exists. It then uses the Get-ChildItem cmdlet to retrieve a listing of all the module files in a particular folder. The –Recurse parameter is used retrieve all the module files in the path. The resulting FileInfo objects are pipelined to the ForEach-Object cmdlet. The fullname property of each FileInfo object is passed to the Copy-Module function. This is shown here:

Test-ModulePath
Get-ChildItem -Path C:\fso -Include *.psm1,*.psd1 -Recurse |
Foreach-Object { Copy-Module -name $_.fullName }

The complete Copy-Modules.ps1 script is seen here.

Copy-Modules.ps1

Function Get-OperatingSystemVersion
{
 (Get-WmiObject -Class Win32_OperatingSystem).Version
} #end Get-OperatingSystemVersion

Function Test-ModulePath
{
 $VistaPath = "$env:userProfile\documents\WindowsPowerShell\Modules"
 $XPPath =  "$env:Userprofile\my documents\WindowsPowerShell\Modules"
 if ([int](Get-OperatingSystemVersion).substring(0,1) -ge 6)
   {
     if(-not(Test-Path -path $VistaPath))
       {
         New-Item -Path $VistaPath -itemtype directory | Out-Null
       } #end if
   } #end if
 Else
   { 
     if(-not(Test-Path -path $XPPath))
       {
         New-Item -path $XPPath -itemtype directory | Out-Null
       } #end if
   } #end else
} #end Test-ModulePath

Function Copy-Module([string]$name)
{
 $UserPath = $env:PSModulePath.split(";")[0]
 $ModulePath = Join-Path -path $userPath `
               -childpath (Get-Item -path $name).basename
 New-Item -path $modulePath -itemtype directory | Out-Null
 Copy-item -path $name -destination $ModulePath | Out-Null
}

# *** Entry Point to Script ***
Test-ModulePath
Get-ChildItem -Path C:\fso -Include *.psm1,*.psd1 -Recurse |
Foreach-Object { Copy-Module -name $_.fullName }

Note: Scripting support does not need to be enabled in Windows PowerShell to use modules. However, to run the Copy-Modules.ps1 to install modules to the users profile, you will need script support. To enable scripting support in Windows PowerShell, use the Set-ExecutionPolicy cmdlet. You could also use Xcopy to copy modules to the user' modules folder.

An easy way to work with modules is to create a couple of Windows PowerShell drives using the filesystem provider. Because the modules live in a location that is not easily navigated to from the command line, and because the $PSModulePath returns a string that contains the path to both the users' and the system modules folders, it makes sense to provide an easier way to work with the modules location. To create a Windows PowerShell drive for the user module location, you use New-PSDrive cmdlet, specify a name such as mymods, use the filesystem provider, and obtain the root location from the $PSModulePath environmental variable by using the split method from the .NET Framework string class. For the users' modules folder, you use the first element from the returned array. This is shown here:

PS C:\> New-PSDrive -Name mymods -PSProvider filesystem -Root `
(($env:PSModulePath).Split(";")[0])

WARNING: column "CurrentLocation" does not fit into the display and was removed
.

Name           Used (GB)     Free (GB) Provider      Root
----           ---------     --------- --------      ----
mymods                           47.62 FileSystem    C:\Users\administrator....

The command to create a Windows PowerShell drive for the system module location is exactly the same as the one used to create a Windows PowerShell drive for the user module location, with the exception of specifying a different name such as sysmods and choosing the second element from the array you obtain by using the split method from on the $PSModulePath variable. This command is seen here:


PS C:\> New-PSDrive -Name sysmods -PSProvider filesystem -Root `
(($env:PSModulePath).Split(";")[1])

WARNING: column "CurrentLocation" does not fit into the display and was removed
.

Name           Used (GB)     Free (GB) Provider      Root
----           ---------     --------- --------      ----
sysmods                          47.62 FileSystem    C:\Windows\System32\Win...

Note: I like to create my Windows PowerShell drives for the two module locations in my personal profile. In this way, the drives are always available when I need to utilize them.

You can also write a script that creates Windows PowerShell drives for each of the two module locations. To do this, you first create an array of names for the Windows PowerShell drives. You then use a for statement to walk through the array of Windows PowerShell drive names, and call the New-PSDrive cmdlet. Because you are running the commands inside a script, it means the new Windows PowerShell drives by default would live within the script scope. After the script ends, the script scope goes away. This means the Windows PowerShell drives would not be available after the script ended, which would defeat our purposes in creating them in the first place. To combat this scoping issue, you need to create the Windows PowerShell drives within the Global scope, which means they will be available in the Windows PowerShell console after the script has completed running. To avoid displaying confirmation messages when creating the Windows PowerShell drives, you pipe the results to the Out-Null cmdlet.

In the New-ModulesDrive.ps1 script, another function is created. This function displays global filesystem Windows PowerShell drives. When the script is run, the New-ModuleDrives function is called. It is followed by calling the Get-FileSystemDrives function. The complete New-ModulesDrive.ps1 script is shown here.

New-ModulesDrive.ps1

Function New-ModuleDrives
{
<#
    .SYNOPSIS
    Creates two PSDrives: myMods and sysMods
    .EXAMPLE
    New-ModuleDrives
    Creates two PSDrives: myMods and sysMods. These correspond
    to the users' modules folder and the system modules folder respectively.
#>
 $driveNames = "myMods","sysMods"

 For($i = 0 ; $i -le 1 ; $i++)
 {
  New-PsDrive -name $driveNames[$i] -PSProvider filesystem `
  -Root ($env:PSModulePath.split(";")[$i]) -scope Global |
  Out-Null
 } #end For
} #end New-ModuleDrives

Function Get-FileSystemDrives
{
<#
    .SYNOPSIS
    Displays global PS Drives that use the Filesystem provider
    .EXAMPLE
    Get-FileSystemDrives
    Displays global PS Drives that use the Filesystem provider
#>
 Get-PSDrive -PSProvider FileSystem -scope Global
} #end Get-FileSystemDrives

# *** EntryPoint to Script ***
New-ModuleDrives
Get-FileSystemDrives

 

KG, that is all there is to using Windows PowerShell modules. Windows PowerShell Module Week will continue tomorrow.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys