Installing Windows PowerShell Modules on Multiple User’s Computers–Updated

A few years ago (2010, wow 4 years now) Ed Wilson, aka the Scripting Guy, wrote a blog post on how to copy PowerShell modules onto multiple user’s computers. You’ll find the original version of his script at:

http://blogs.technet.com/b/heyscriptingguy/archive/2010/01/19/hey-scripting-guy-january-19-2010.aspx

Although I got my copy from the new version of his excellent book, Windows PowerShell 4.0 Best Practices.

I have used this for a while, but it turned out there was a drawback. Let me explain.

I’m currently developing what is becoming a rather large module. For management, I wanted to break it down into smaller pieces. The solution is to put logical groupings of functions into individual PS1 files, then dot source those from the module.

. $PSScriptRoot\MyPiecesPartsScript.ps1

Then I could break it up as much as I wanted. The problem with the original script was it only copied PSM1 and PSD1 files. In his script he has a Get-ChildItem which feeds his Copy-Module function. When I added *.PS1 to the list of things to include, the script created a folder for each script. Not what I wanted.

The solution for me was to write another function which would get all the folders in my source and copy the PS1s to the appropriate module folder. I had one other criteria though. In each of my module folders I have a test script where I can test out my module. I name these with the same name as the module but with a –Test on the end. Naturally I don’t want to copy these to the users module folder.

Here then is the function I created. You could copy and paste this right below the functions in Ed’s script:

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, at the bottom of Ed’s original script you can use:

Get-ChildItem -Path C:\PS\Arcane-Modules -Directory |
  ForEach-Object { Copy-SupportingScripts -Folder $_ -Exclude *-Test.ps1 -Verbose }

Here is the final result of my script merged with Ed’s. Make sure to give him plenty of kudo’s for the original.

# —————————————————————————–
# Script: Copy-Modules.ps1
# Author: ed wilson, msft
# Date: 09/07/2013 17:33:15
# Updated by: Robert C. Cain, @ArcaneCode, Pragmatic Works
# Updated Date: 02/20/2014
# Keywords: modules
# comments: installing
# Windows PowerShell 4.0 Best Practices, Microsoft Press, 2013
# Chapter 10
# —————————————————————————–
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 ( (Test-Path $modulePath) -eq $false)
   { New-Item -path $modulePath -itemtype directory | Out-Null }
Copy-Item -path $name -destination $ModulePath -force | Out-Null

}

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
    }
  }
}

# *** Entry Point to Script ***
$sourceFolder = "C:\PS\Arcane-Modules"

# Ensure the PowerShell folder exists in the users Documents folder
Test-ModulePath

# Copy the modules (psd1 and psm1) files
Get-ChildItem -Path $sourceFolder -Include *.psm1,*.psd1 -Recurse |
  ForEach-Object { Copy-Module -name $_.fullName }

# Copy any supporting ps1 files.
# Remove the -Exclude directive if you don’t want to exclude anything.
Get-ChildItem -Path $sourceFolder -Directory |
  ForEach-Object { Copy-SupportingScripts -Folder $_ `
                                          -Exclude *-Test.ps1 `
                                          -Verbose
                 }

One final and very important note. Ed’s original script was written in the PowerShell v2 days. My function uses the new –Directory switch introduced in PowerShell v3, so you will need at least v3 to make this work.