Overview
Rambling
The first weeks of August 2016 were great. I spent most of my time relaxing on the porch, with some sailing, swimming, and ice cream mixed in.
Of course, I couldn’t help myself, and spent a little time on a fun side project: PSDepend
PSDepend
What is this PSDepend thing? Long story short, it’s a way for you to tell PowerShell that you need certain PowerShell modules, git repositories, and other dependencies, using a small requirements.psd1 file.
What’s a requirements.psd1?
Here’s a quick example to illustrate the basics of a requirements.psd1
:
@{
psake = 'latest'
Pester = 'latest'
BuildHelpers = '0.0.20'
PSDeploy = '0.1.21'
'RamblingCookieMonster/PowerShell' = 'master'
}
This is pretty straightforward: from the PowerShell Gallery, we want the latest Pester and psake, and specific versions of BuildHelpers and PSDeploy. We also want to download a GitHub repo’s master branch.
I can now run Invoke-PSDepend
against this requirements file, and pull in my dependencies.
Before we dive in, why would we want something like this?
Why PSDepend?
You can probably come up with more creative scenarios, but here are a few use cases that came to mind:
- Provide functional dependency documentation for a project
- Enable something like a module-focused virtual environment for PowerShell
- Simplify sharing code with non-PowerShell-savvy folks
- Help ensure a consistent runtime environment and improve portability for PowerShell solutions
- Quickly set up a fresh dev/test/etc. PowerShell environments
How many times have you had to describe the steps to resolve prerequisites for a function, module, script, or other PowerShell code? Did you have to start from ground zero and explain what a module is and how to install it?
It turns out, many other languages have solved this problem. In Python, you might pip install -r requirements.txt
. In Ruby, you might bundle install
. In PowerShell? You hard code solution-specific logic into your code, abstract this out into configuration management, or just manually install the prerequisites and hope you don’t forget about them if you re-deploy your system.
Shouldn’t something like this already exist? Michael Willis has already started on the awesome looking PSRequire - unfortunately, I still need to support PowerShell pre-v5, so the class-based PSRequire wasn’t for me; time for a side project!
What dependencies does PSDepend support?
At the time of writing, we support a few dependency types:
- PSGalleryModule: Modules from the PowerShell Gallery, via PowerShellGet
- Package: Install a package using the PackageManagement module
- PSGalleryNuget: Modules from the PowerShell Gallery, without the need of PowerShellGet
- Git: Clone a repo and checkout a branch/commit/tag
- GitHub: Download a specific branch or commit from a repo on GitHub, and extract a PowerShell module if we find it (Thanks to Doug Finke for some starter code!)
- FileDownload: Download a file
- FileSystem: Copy a file or folder
- Task: Run a pre-defined PowerShell script
- Command: Run a PowerShell command
More to come - Nuget for example - but we’d love ideas or pull requests! You can always run Get-PSDependType
to view available dependency types.
That syntax seems too simplistic
It probably is! That being said, the simple syntax we showed with a ModuleName = 'Version'
and ’Account/Repo’ = 'Version'
is just a shortcut. We also support a more flexible syntax:
@{
PSDeploy_0_1_21 = @{
DependencyType = 'PSGalleryNuget'
Name = 'PSDeploy'
Version = '0.1.21'
Target = "C:\ProjectX"
Tags = 'prod'
DependsOn = 'BuildHelpers'
AddToPath = $True
PostScripts = 'C:\SomeScripts.ps1'
}
# You can still mix in simple syntax
BuildHelpers = '0.0.20'
}
Whew! That was a bit convoluted. What does it actually do?
- We make sure to install the BuildHelpers dependency first (
DependsOn
) - We download PSDeploy, version 0.1.21, from the PowerShell Gallery (
Name
,Version
,DependencyType
) - We use a nuget.exe (and grab it for you if you don’t have it), rather than depend on PowerShellGet (PSGalleryNuget
DependencyType
) - We install this to
C:\ProjectX\PSDeploy
(Target
) - We only run the PSDepend install if
Invoke-PSDepend
is called with-Tags prod
(Tags
) - We add
C:\ProjectX
(Target
) to $ENV:PSModulePath (AddToPath
) - We run
C:\SomeScripts.ps1
after we’ve downloaded PSDeploy (PostScripts
)
You can run Get-Help Get-Dependency
, or Get-Help about_PSDepend_Definitions
for details on standard attributes, and Get-PSDependType SomeTypeName -ShowHelp
to see the help for a specific dependency type.
Let’s dive in and start using PSDepend.
Getting Started with PSDepend
Getting up and running with PSDepend is simple!
# PowerShell 5 or PowerShellGet installed:
Install-Module PSDepend
# PowerShell 3 or 4, curl|bash bootstrap. Read before running something like this...
Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://raw.github.com/ramblingcookiemonster/PSDepend/Examples/Install-PSDepend.ps1')
# Git
# Download the repository
# Unblock the zip
# Extract the PSDepend folder to a module path (e.g. $env:USERPROFILE\Documents\WindowsPowerShell\Modules\)
# Import and start exploring
Import-Module PSDepend
Get-Command -Module PSDepend
Get-Help about_PSDepend
That’s it! Keep in mind we bootstrap nuget.exe when you import the module for the first time, unless you have it in your path or adjust the config file.
Now that you have PSDepend, we can start kicking the tires.
What commands can I run?
PSDepend includes commands to do things, and to help explore PSDepend:
Get-PSDependType
This is a quick way to see what dependency types are available:
Get-PSDependType
DependencyType Description DependencyScript
-------------- ----------- ----------------
FileDownload Download a file C:\...PSDependScripts\FileDownload.ps1
FileSystem Copy a file or folder C:\...PSDependScripts\FileSystem.ps1
Git Clone a git repository C:\...PSDependScripts\Git.ps1
PSGalleryModule Install a PowerShell m... C:\...PSDependScripts\PSGalleryModule.ps1
PSGalleryNuget Install a PowerShell m... C:\...PSDependScripts\PSGalleryNuget.ps1
Task Support dependencies b... C:\...PSDependScripts\Task.ps1
...
You can also use it to get help information for one of these types:
Get-PSDependType PSGalleryModule -ShowHelp
...
SYNOPSIS
Installs a module from a PowerShell repository like the PowerShell Gallery.
SYNTAX
C:\Users\wframe\Documents\GitHub\PSDepend\PSDepend\PSDependScripts\PSGalleryModule.ps1 [[-Dependency] <PSObject[]>] [[-Repository]
<String>] [-Force] [-Import] [<CommonParameters>]
DESCRIPTION
Installs a module from a PowerShell repository like the PowerShell Gallery.
Relevant Dependency metadata:
Name: The name for this module
Version: Used to identify existing installs meeting this criteria, and as RequiredVersion for installation. Defaults to 'latest'
Target: Used as 'Scope' for Install-Module. If this is a path, we use Save-Module with this path. Defaults to 'AllUsers'
AddToPath: If target is used as a path, prepend that path to ENV:PSModulePath
...
What else can we do?
Get-Dependency
If you want to parse a requirements.psd1 file, you can run Get-Dependency
.
By default this will look for a requirements.psd1 or a *.depend.psd1 file in the current path. You can also use -Recurse
, or specify a -Path
. Here’s the output from our flexible psd1 example above:
Get-Dependency | Select-Object -Property *
DependencyFile : C:\temp\requirements.psd1
DependencyName : BuildHelpers
DependencyType : PSGalleryModule
Name : BuildHelpers
Version : 0.0.20
Parameters :
Source :
Target :
AddToPath :
Tags :
DependsOn :
PreScripts :
PostScripts :
PSDependOptions :
Raw :
DependencyFile : C:\temp\requirements.psd1
DependencyName : PSDeploy_0_1_21
DependencyType : PSGalleryNuget
Name : PSDeploy
Version : 0.1.21
Parameters :
Source :
Target : C:\ProjectX
AddToPath : True
Tags : prod
DependsOn : BuildHelpers
PreScripts :
PostScripts : C:\SomeScripts.ps1
PSDependOptions :
Raw : {Tags, Version, AddToPath...}
Notice that BuildHelpers is listed first (PSDeploy_0_1_21 DependsOn it). You can use Get-Dependency
to verify that PSDepend will read your requirements.psd1 file as expected.
So! We know how to read a requirements file, but how do we actually install these dependencies?
Invoke-PSDepend
Invoke-PSDepend can kick off a test, install, or import of a dependency. By default, it will search recursively for any requirements.psd1 file, or *.depend.psd1 file under the current path, similar to Invoke-Pester
or Invoke-PSDeploy
, and run the install action.
Using the same requirements.psd1, let’s verify that our dependencies aren’t already in place:
Invoke-PSDepend C:\temp\requirements.psd1 -Test |
Select-Object -Property Dependency*
DependencyFile DependencyName DependencyType DependencyExists
-------------- -------------- -------------- ----------------
C:\temp\requirements.psd1 BuildHelpers PSGalleryModule False
C:\temp\requirements.psd1 PSDeploy_0_1_21 PSGalleryNuget False
Invoke-PSDepend C:\temp\requirements.psd1 -Test -Quiet
False
Now, let’s kick off PSDepend:
Invoke-PSDepend -Path C:\temp
After a couple seconds, our dependencies are in place:
Invoke-PSDepend C:\temp\requirements.psd1 -Test –Quiet
True
We can verify that the Target for PSDeploy was added to the PSModulePath:
$ENV:PSModulePath
C:\ProjectX;C:\Users\wframe...
Perhaps we want to import these dependencies, and validate that they imported:
Invoke-PSDepend C:\temp\requirements.psd1 -Import
Get-Module -Name BuildHelpers, PSDeploy |
Select-Object -Property Version, Path
Version Path
------- ----
0.0.20 C:\Program Files\WindowsPowerShell\Modules\BuildHelpers\0.0.20\BuildHelpers.psm1
0.1.21 C:\ProjectX\PSDeploy\PSDeploy.psm1
Enough blather, let’s cover a practical scenario where this might come in handy!
Example: Module Based Virtual Environments
PSDepend can help create minimalistic virtual environments in a few ways:
- Prepending
$ENV:Path
with one or more paths - Prepending
$ENV:PSModulePath
with one or more paths - Importing modules
- Installing modules and files in deterministic locations
Let’s pretend we’re working on ProjectX. Our systems may have different versions of modules, perhaps due to conflicting requirements from other projects. This is a common theme with languages like Python and Ruby, and will likely become more of an issue in PowerShell.
Do we just replace those modules and break things? Do we need to include our own custom code for dependencies with each of our projects, perhaps using PowerShell 5’s side-by-side versioning? No thank you, I’ll try this with PSDepend!
Demo
Once we’ve identified our dependencies, we build a requirements.psd1 file:
@{
# Set some global, override-able defaults
PSDependOptions = @{
Target = 'C:\ProjectX'
}
# Grab some modules
PSSlack = '0.0.15'
ImportExcel = '2.2.7'
'Posh-SSH' = 'latest'
# Download a GitHub repo
'ramblingcookiemonster/PowerShell' = 'master'
# Grab an internal module
'PSAMS' = @{
DependencyType = 'FileSystem'
Source = '\\FileServer01\PowerShell\PSAMS'
}
# Download a file
'psrabbitmq.dll' = @{
DependencyType = 'FileDownload'
Source = 'https://github.com/RamblingCookieMonster/PSRabbitMq/raw/master/PSRabbitMq/lib/RabbitMQ.Client.dll'
}
}
I’ll save this in C:\ProjectX\requirements.psd1, and invoke it:
Invoke-PSDepend C:\ProjectX -Force
We can verify that we have the resulting files:
Invoke-PSDepend C:\ProjectX -Test –Quiet
True
Get-ChildItem C:\ProjectX | Select-Object -Property Name
Name
----
ImportExcel
Posh-SSH
PowerShell
psams
PSSlack
RabbitMQ.Client.dll
requirements.psd1
Finally, if we need these modules in our project, we can import them with a one liner:
# Import dependencies!
Invoke-PSDepend C:\ProjectX –Import -Force
# Trust, but verify
Get-Module -Name PSSlack, ImportExcel, Posh-SSH, PSAMS
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 2.2.7 ImportExcel {Add-WorkSheet, BarChart, Col...
Manifest 1.7.6 Posh-SSH {Get-SCPFile, Get-SCPFolder, ...
Script 0.0.1 psams {Get-AmsLab, Get-AmsLabMember...
Script 0.0.15 PSSlack {Find-SlackMessage, Get-PSSla...
That’s it! A small requirements.psd1 file, and we now have our modules loaded and available to re-use in C:\ProjectX
, even if our $ENV:PSModulePath
includes these modules at incompatible versions.
Next Steps
So! An early version of PSDepend is out there, you can find the raw code on GitHub.
If this is something you might use, kick the tires a bit! Feel free to submit an issue for an idea or bug, or a pull request to fix bugs, code smells, documentation, lack of tests, or anything else that you think might help.
Cheers!