/PSDepend

PowerShell Dependency Handler

Primary LanguagePowerShellMIT LicenseMIT

Build status

This is a fork of PSDepend as the master repo appears to have been abandoned

I've created this so I fix some issues that are causing me pain. such as os detection not working correctly ps7 on windows. Please submit Issues/PR's if you encounter issues.

PSDepend2

This is a simple PowerShell dependency handler. You might loosely compare it to bundle install in the Ruby world or pip install -r requirements.txt in the Python world.

PSDepend allows you to write simple requirements.psd1 files that describe what dependencies you need, which you can invoke with Invoke-PSDepend

WARNING:

  • Minimal testing. This is in my backlog, but PRs would be welcome!
  • This borrows quite heavily from PSDeploy. There may be leftover components that haven't been adapted, have been improperly adapted, or shouldn't have been adapted
  • Would love ideas, feedback, pull requests, etc., but if you rely on this, consider pinning a specific version to avoid hitting breaking changes.

Getting Started

Installing PSDepend

# PowerShell 5
Install-Module PSDepend

# PowerShell 3 or 4, curl|bash bootstrap. Read before running something like this : )
iex (new-object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/RamblingCookieMonster/PSDepend/master/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

Example Scenarios

In-depth:

Recipes:

Defining Dependencies

Store dependencies in a PowerShell data file, and use *.depend.psd1 or requirements.psd1 to allow Invoke-PSDepend to find your files for you.

What does a dependency file look like?

Simple syntax

Here's the simplest syntax. If this meets your needs, you can stop here:

@{
    psake        = 'latest'
    Pester       = 'latest'
    BuildHelpers = '0.0.20'  # I don't trust this Warren guy...
    PSDeploy     = '0.1.21'  # Maybe pin the version in case he breaks this...

    'RamblingCookieMonster/PowerShell' = 'master'
}

And what PSDepend sees:

DependencyName                   DependencyType  Version Tags
--------------                   --------------  ------- ----
psake                            PSGalleryModule latest
BuildHelpers                     PSGalleryModule 0.0.20
Pester                           PSGalleryModule latest
RamblingCookieMonster/PowerShell GitHub          master
PSDeploy                         PSGalleryModule 0.1.21

There's a bit more behind the scenes - we assume you want PSGalleryModules or GitHub repos unless you specify otherwise, and we hide a few dependency properties.

We can also indicate the dependency type more explicitly if desired:

@{
    'PSGalleryModule::InvokeBuild' = 'latest'
    'GitHub::RamblingCookieMonster/PSNeo4j' = 'master'
}

Flexible syntax

What else can we put in a dependency? Here's an example using a more flexible syntax. You can mix and match.

@{
    psdeploy = 'latest'

    buildhelpers_0_0_20 = @{
        Name = 'buildhelpers'
        DependencyType = 'PSGalleryModule'
        Parameters = @{
            Repository = 'PSGallery'
            SkipPublisherCheck = $true
        }
        Version = '0.0.20'
        Tags = 'prod', 'test'
        PreScripts = 'C:\RunThisFirst.ps1'
        DependsOn = 'some_task'
    }

    some_task = @{
        DependencyType = 'task'
        Target = 'C:\RunThisFirst.ps1'
        DependsOn = 'nuget'
    }

    nuget = @{
        DependencyType = 'FileDownload'
        Source = 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe'
        Target = 'C:\nuget.exe'
    }
}

This example illustrates using a few different dependency types, using DependsOn to sort things (e.g. some_task runs after nuget), tags, and other options.

You can inspect the full output as needed. For example:

# List the dependencies, get the third item, show all props
$Dependency = Get-Dependency \\Path\To\complex.depend.ps1
$Dependency[2] | Select *
DependencyFile : \\Path\To\complex.depend.psd1
DependencyName : buildhelpers_0_0_20
DependencyType : PSGalleryModule
Name           : buildhelpers
Version        : 0.0.20
Parameters     : {Repository,SkipPublisherCheck}
Source         :
Target         :
AddToPath      :
Tags           : {prod, test}
DependsOn      : some_task
PreScripts     : C:\RunThisFirst.ps1
PostScripts    :
Raw            : {Version, Name, Tags, DependsOn...}

Note that we replace certain strings in Target and Source fields:

  • $PWD (or .) refer to the current path
  • $ENV:USERPROFILE, $ENV:TEMP, $ENV:ProgramData, $ENV:APPDATA
  • Variables need to be in single quotes or the $ needs to be escaped. We replace the raw strings with the values for you. This will not work: Target = "$PWD\dependencies". This will: Target = '$PWD\dependencies'
  • If you call Invoke-PSDepend -Target $Something, we override any value for target
  • Thanks to Mike Walker for the idea!

Repository Credentials

If you are using a PowerShell module repository that requires authentication then add those to your dependency. When working with credentials there are two parts we need to consider:

  • Credential property of our dependency.
  • Credentials parameter for Invoke-PSDepend.
@{
    psdeploy = 'latest'

    buildhelpers_0_0_20 = @{
        Name = 'buildhelpers'
        DependencyType = 'PSGalleryModule'
        Parameters = @{
            Repository = 'PSGallery'
            SkipPublisherCheck = $true
        }
        Version = '0.0.20'
        Credential = 'must_match'
    }
}

Now create a PSCredential object with the credentials to access the repository and run it:

Invoke-PSDepend -Path C:\requirements.psd1 -Credentials @{ 'must_match' = $creds }

Make sure whatever you use as must_match is the same in the dependency as it is in the hashtable you pass to the Credentials parameter.

Exploring and Getting Help

Each DependencyType - PSGalleryModule, FileDownload, Task, etc. - might treat these standard properties differently, and may include their own Parameters. For example, in the BuildHelpers node above, we specified a Repository and SkipPublisherCheck parameters.

How do we find out what these mean? First things first, let's look at what DependencyTypes we have available:

Get-PSDependType
DependencyType  Description                                                 DependencyScript
--------------  -----------                                                 ----------------
PSGalleryModule Install a PowerShell module from the PowerShell Gallery.    C:\...\PSDepend\PSDepen...
Task            Support dependencies by handling simple tasks.              C:\...\PSDepend\PSDepen...
Noop            Display parameters that a depends script would receive...   C:\...\PSDepend\PSDepen...
FileDownload    Download a file                                             C:\...\PSDepend\PSDepen...

Now that we know what types are available, we can read the comment-based help. Hopefully the author took their time to write this:

Get-PSDependType -DependencyType PSGalleryModule -ShowHelp
...
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'

PARAMETERS
...
    -Repository <String>
        PSRepository to download from.  Defaults to PSGallery
    -SkipPublisherCheck <Switch>
        Bypass the catalog signing check.  Defaults to $false

In this example, we see how PSGalleryModule treats the Name, Version, and Target in a depend.psd1, and we see Parameter's specific to this DependencyType, 'Repository' and 'SkipPublisherCheck'

Finally, we have a few about topics, and individual commands have built in help:

Get-Help about_PSDepend
Get-Help about_PSDepend_Definitions
Get-Help Get-Dependency -Full

Extending PSDepend

PSDepend is extensible. To create a new dependency type:

  • Pick a name. We'll use Nothing as an example
  • Create DependencyType.ps1 (substituting in your name, e.g. Nothing.ps1) in the PSDependScripts folder
  • Your DependencyType.ps1 (Nothing.ps1 in this example) should...
    • Have comment based help
    • Include details on how you use Dependency metadata. For example, in Git.ps1, Version is used in git checkout
    • Include a PSDependAction parameter that takes Install, Test, Import, or a subset of these. Example parameter declaration
    • Depending on which PSDependAction is specified by the user, your script should install, test (return true or false depending on whether the dependency exists - sometimes this is impossible to check), and import (import the dependency - if appropriate, this might import a module or dot source code, for example)
  • Add your new dependency type to PSDependMap.psd1

So! In our example, we would create PSDepend\PSDependScripts\Nothing.ps1, with the following code:

<#
    .SYNOPSIS
        Example Dependency

    .DESCRIPTION
        Example Dependency

        Relevant Dependency metadata:
            Version: Used for nonsense output

    .PARAMETER Dependency
        Dependency to process

    .PARAMETER StringParameter
        An example parameter that does nothing

    .PARAMETER PSDependAction
        Test, Install, or Import the dependency.  Defaults to Install

        Test: Return true or false on whether the dependency is in place
        Install: Install the dependency
        Import: Import the dependency
#>
[cmdletbinding()]
param (
    [PSTypeName('PSDepend.Dependency')]
    [psobject[]]$Dependency,

    [ValidateSet('Test', 'Install', 'Import')]
    [string[]]$PSDependAction = @('Install'),

    [string]$StringParameter
)

$Output = [PSCustomobject]@{
    DependencyName = $Dependency.DependencyName
    Status = "Invoking $PSDependAction action"
    BoundParameters = $PSBoundParameters.Keys
    Message = "Version [$Version]"
}

# Notice that we end the script if we're testing.
if( $PSDependAction -Contains 'Test' )
{
    Write-Verbose $Output
    return $true
}

$Output

Finally, we'll add an entry to PSDependMap.psd1:

    Nothing = @{
        Script= 'Nothing.ps1'
        Description = 'Example dependency'
    }

Lastly, we'll define a requirements.psd1 using this dependency:

@{
    ExampleDependency = @{
        DependencyType = 'Nothing'
        Version = 1
        Parameters = @{
            StringParameter = 'A thing'
        }
    }
}

Finally, run it!

Invoke-PSDepend -Path C:\requirements.psd1 -Test -Quiet

True

Invoke-PSDepend -Path C:\requirements.psd1
DependencyName    Status                  BoundParameters                               Message
--------------    ------                  ---------------                               -------
ExampleDependency Invoking Install action {StringParameter, PSDependAction, Dependency} Version [1]
Invoke-PSDepend -Path C:\requirements.psd1 -Import
DependencyName    Status                 BoundParameters                               Message
--------------    ------                 ---------------                               -------
ExampleDependency Invoking Import action {StringParameter, PSDependAction, Dependency} Version [1]

Notes

Major props to Michael Willis for the idea - check out his PSRequire, a similar but more feature-full solution.