Warren F bio photo

Warren F

Systems Engineer with a penchant for PowerShell, science, cooking, information security, family, cookies, and the Oxford comma.

Connect

@pscookiemonster LinkedIn Github Stackoverflow TechNet RSS Feed My old blog

My GitHub Repos

AppVReporting BuildHelpers Citrix.NetScaler Git-Presentation InfoBlox Invoke-Parallel PowerShell PSDepend PSDeploy PSDiskPart PSExcel PSHTMLTable PSRabbitMQ PSSlack PSSQLite PSStash RabbitMqTools SecretServer

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.

cape

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!

Thumbnail credit