Hey, Scripting Guy! How Can I Install Windows PowerShell Modules on Multiple Users' Computers?

Hey, Scripting Guy! How Can I Install Windows PowerShell Modules on Multiple Users' Computers?

  • Comments 6
  • Likes

Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I really dig that I can have a module that contains functions and things. This seems like it would be groovy to use to customize my Windows PowerShell environment. The thing is that I do not want to be hopping around from desk to desk from user to user to copy module files into esoteric and obscure locations. There needs to be an Install-Module cmdlet that can be used to perform this service for me. Can you help me?

-- EH

 

Hey, Scripting Guy! AnswerHello EH,

Microsoft Scripting Guy Ed Wilson here. It is snack time in Charlotte, North Carolina. This morning I am sipping a cup of English Breakfast tea, with a little lemon grass, and a cinnamon stick while munching on raw walnuts and artisan cheese. I have been hanging out on Twitter, answering questions, making new friends, gathering ideas for research, and deleting spam in the scripter@microsoft.com e-mail inbox. Like most IT pros, I can multitask. However, I will admit the snacking is getting a few extra MIPS right now.

EH, you do not want to be hopping around from desk to desk? Actually hopping around is fun. When I was in Australia doing a train-the-trainer session for the Windows PowerShell class I wrote, I used to sit outside in the twilight and mellow out. One day while I was relaxing, I saw a giant rabbit hopping through the yard. I quickly grabbed my camera and snapped the following photo.

Image of "giant Australian rabbit"

 

Okay, so he is not really a rabbit, but my friend Chris assured me that kangaroos in Australia are like rabbits from the southern United States. Awesome! It is an inspiring site to be sitting on your back porch and watch these dudes go hopping through the back yard.

Note: Portions of today's Hey, Scripting Guy! Blog post are excerpted from the Microsoft Press book, Windows PowerShell 2.0 Best Practices by Ed Wilson. The book is now available.

EH, one of the features of modules is that they can be installed without elevated rights. Because each user has a modules folder in their %userprofile% directory that they automatically have rights to use, the installation of a module does not require Administrator rights. An additional feature of modules is that they do not require a specialized installer. The files associated with a module can be copied by using the Xcopy utility, or, as I prefer to do, they can be copied by using the Copy-Item Windows PowerShell cmdlet.

The users' modules folder does not exist by default. To avoid confusion, you may decide to create the modules directory in the users' profile before deploying modules, or you may simply create a module installer script that checks for the existence of the users' modules folder, creates the folder if it does not exist, and then copies the modules. One problem in directly accessing the users' module directory is that it is in different locations, depending on the version of the operating system. In Windows XP and Windows Server 2003, the users' module folder is in the My Documents folder. In Windows Vista and later, the users' module folder is in the Documents folder. In the Copy-Modules.ps1 script, we solve the problem of different module folder locations by using a function, Get-OperatingSystemVersion, which retrieves the major version number of the operating system. The Get-OperatingSystemversion function is seen here:

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

The major version number of the operating system is used in the Test-ModulePath function. If the major version number of the operating system is greater than 6, it means the operating system is at least Windows Vista and will therefore use the Documents folder in the path to the modules. If the major version number of the operating system is not greater than 6, the script will use the My Documents folder for the module location. After the version of the operating system is determined and the path to the module location is ascertained, it is time to determine if the module folders exist. To do this, use the Test-Path cmdlet, which returns a Boolean value. Because you are interested only in the absence of the folder, you can use the –not operator as shown here in the completed Test-ModulePath function:

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

After the users' modules folder has been created, it is time to create a child folder to hold the new module. A module is always installed in a folder that has the same name as the module itself. The name of the module is the file name that contains the module minus the .psm1 extension. This location is shown in the following image.

Image of location of module

 

I wrote the Copy-Modules.ps1 script to ease some of the concerns about installing Windows PowerShell modules on a computer. The complete Copy-Modules.ps1 script is seen here.

Copy-Modules.ps1

<#
   .Synopsis
     Looks for the module path folder, copies module files to folder
   .Description
     This script looks for the module path folderon either xp family
     or vista family computer. if the module folder is missing, it
     will create it. It then copies the module files into the folder.
     When copying new module files, it creates a parent folder for each
     new module as required by the module architecture.
   .Example
     Copy-Modules.ps1 -Path c:\fso
     This command checks for existence of module folder and copies
     all module files from the c:\fso directory into their own folder
     in the users modules directory.
   .Inputs
     [String]
   .OutPuts
     [System.Io.DirectoryInfo]
   .Notes
    NAME:      Copy-Modules.ps1
    AUTHOR:    ed wilson
    LASTEDIT:  4/12/2009
    KEYWORDS:  modules, test-path, new-item, environment
               PowerShell Best Practices
   .Link
     Test-Path
     New-Item
     Get-WmiObject
     Http://www.ScriptingGuys.com
#Requires -Version 2.0
#>
[CmdletBinding()]
Param(
      [Parameter(Position=0,
      Mandatory=$True,
      ValueFromPipeline=$True)]
      [string]$path
     )
     
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
 If(-not(Test-Path -path $modulePath))
   {
    New-Item -path $modulePath -itemtype directory | Out-Null
    Copy-item -path $name -destination $ModulePath | Out-Null
   }
 Else
   {
    Copy-item -path $name -destination $ModulePath | Out-Null
   }
}

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

In the Copy-Module function from the Copy-Modules.ps1 script, the first action that is taken is to retrieve the value of PSModulePath environmental variable. Because there are two locations in which modules can be stored, the PSModulePath environmental variable contains the path to both locations. The PSModulePath is not stored as an array; it is stored as a string. The value contained in PSModulePath is seen here:

 PS C:\> $env:psmodulePath
C:\Users\administrator.NWTRADERS.000\Documents\WindowsPowerShell\Modules;C:\Win
dows\System32\WindowsPowerShell\V1.0\Modules\

If you attempt to index into the data stored in the PSModulePath environmental variable, you will retrieve one letter at a time. This is seen here:

PS C:\> $env:psmodulePath[0]
C
PS C:\> $env:psmodulePath[1]
:
PS C:\> $env:psmodulePath[2]
\
PS C:\> $env:psmodulePath[3]
U

Attempting to retrieve the path to the users' module location one letter at a time would be problematic at best and error prone at worst. Because the data is a string, you can use string methods to manipulate the two paths. To break a string into an array that can be easily utilized, you use the split method from the System.String class. You only need to pass a single value to the split method—the character to split upon. Because the value stored in the PSModulePath variable is a string, you can access the split method directly. This is shown here:

PS C:\> $env:psmodulePath.split(";")
C:\Users\administrator.NWTRADERS.000\Documents\WindowsPowerShell\Modules
C:\Windows\System32\WindowsPowerShell\V1.0\Modules\

You can see from the output above that the first string displayed is the path to the users' modules folder, and the second path is the path to the system modules folder. Because the split method turns a string into an array, it means you can now index into the array and retrieve the path to the users' modules folder by using the [0] syntax. You do not need to use an intermediate variable to store the returned array of paths if you do not wish to do so. You can index into the returned array directly. If you were to use the intermediate variable to hold the returned array and then index into the array, the code would resemble the following:

PS C:\> $aryPaths = $env:psmodulePath.split(";")
PS C:\> $aryPaths[0]
C:\Users\administrator.NWTRADERS.000\Documents\WindowsPowerShell\Modules

Because the array is immediately available after the split method has been called, you directly retrieve the users' modules path. This is seen here:

PS C:\> $env:psmodulePath.split(";")[0]
C:\Users\administrator.NWTRADERS.000\Documents\WindowsPowerShell\Modules

 

EH, that is all there is to installing 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 your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys 

 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Hi,

    great article. A little suggestion to help those that have non-english v1 profiles. In that case the "My Documents" folder has a localized name. Instead of using hard coded documents paths, why not use this?

    [System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::MyDocuments)

    Cheers,

    HA

  • define about network hacking

  • Thanks. This proved to be really useful for me.

  • Great script, I've been using this a while to deploy my modules. I've recently run into a quirk though. I'm developing a module to interact with SSIS packages. As it is becoming rather long, I wanted to break it into smaller files, logically grouping functions into individual ps1 files. In the module I'd simply load it via dot sourcing. . $psScriptRoot\SSIS-Variables.ps1 . $psScriptRoot\SSIS-Packages.ps1 and so forth. The script above though breaks down with the addition of PS1 files in the module folder. If I add *.ps1 to the -Include, it creates a new folder for each PS1 file rather than copying the PS1 files into the same folder as the module. Just wondering if you had a quick suggestion on how to fix, before I start brutalizing your script (LOL). Thanks, Robert | @ArcaneCode

  • Well like most coders right as I hit the post button I thought of a way to do it I wanted to be able to copy supporting PS1 files over, but at the same time I wanted to be able to exclude certain files. For example, I keep a test file in the same folder as the module I am developing which ends in -Test.ps1. It gives me a place I can test out my module. I wrote a function (hopefully this will copy and paste OK, I'll try to make a blog post as well). This was written on a PS4 box, it should also work in PS3, but won't work in PS2. function Copy-SupportingScripts () { [CmdletBinding()] param ([Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Please pass a Directoy object.' )] [System.IO.DirectoryInfo] $Folder, [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Enter files to exclude.' )] [string] $Exclude ) foreach($dir in $Folder) { $UserPath = $env:PSModulePath.split(";")[0] $targetPath = "$UserPath\$($dir.Name)" $sourcePath = "$($dir.FullName)\*.ps1" Write-Verbose "Copy $sourcePath to $targetPath" if ($Exclude.Length -gt 0) { Write-Verbose " Excluding $Exclude in the copy." Copy-Item -Path $sourcePath ` -Destination $targetPath ` -Exclude *-Test.ps1 ` -Force | Out-Null } else { Copy-Item -Path $sourcePath ` -Destination $targetPath ` -Force | Out-Null } } } To call it, use: Get-ChildItem -Path C:\PS\Arcane-Modules -Directory | ForEach-Object { Copy-SupportingScripts -Folder $_ -Exclude *-Test.ps1 -Verbose } Hope someone finds it a useful addition to your script. Robert | @ArcaneCode

  • Well that pasted in really badly, so I made a blog post out of it. Again, hope you folks will find it helpful. http://arcanecode.com/2014/02/20/installing-windows-powershell-modules-on-multiple-users-computersupdated/