Warren F bio photo

Warren F

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


@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



A short while back I gave my first live webinar, on getting started with Git and GitHub. It was both exciting and a bit terrifying. I’m gearing up for similar sessions at work to get folks going with our version control solution, Stash.

When I look at version control as an IT professional, one pain point that we could help with stands out. In addition to changing their workflow and learning version control, folks are now working with files and folders in source control. Not where they live. Wouldn’t it be handy if we could use version control, and not worry about remembering to be sure to move the files to their appropriate homes?

This post will discuss a simple PowerShell based deployment solution: PSDeploy.

Continuous Deployment

Developers realized this was important long ago. There are some specialized deployment tools like Octopus Deploy, continuous integration and deployment tools like AppVeyor or TeamCity, and continuous integration tools like Jenkins that can be shoehorned into providing deployments.

So, with all these solutions, why duct-tape something together?

  • I like fun side projects
  • A simplified deployment system might help with buy in for our version control solution
  • We use Jenkins. Their build definition process leaves a bit to be desired. This allows me to use the same abstracted build script for every project, as long as I have a deployments.yml in the repo root
  • This is portable, and might help folks who use more than one tool chain. I could use PSDeploy with Jenkins, TeamCity, AppVeyor, Bamboo, etc.

Let’s dive in.


This is a quick and dirty module that simplifies deployments. You create a deployment config file, PSDeploy reads it and runs your deployment(s).


  • YAML: Data format for deployment config files. Even easier to read than JSON. Familiar to folks using tools like AppVeyor.
  • Deployment config: YAML files defining what is being deployed. They should have a source, a destination, a deployment type, and might contain freeform deployment options.
  • Deployment type: These define how to actually deploy something. Each type is associated with a script. We’re including FileSystem and FileSystemRemote to start, but this is extensible.
  • Deployment script: These are scripts associated with a particular deployment type. All should accept a ‘Deployment’ parameter. For example, the FileSystem script uses robocopy and copy-item to deploy folders and files, respectively.

A Simple Illustration

Let’s looks at an example deployments.yml and the outcome of invoking it.

Here’s a project I’m working on. I want to deploy MyModule to a few paths every time I push a commit. I should probably gate this with Pester tests as well : )

Module folder

Here are the content of the deployments.yml:

  Author: 'wframe'
    - 'MyModule'
    - 'C:\Temp\MyModule'
    - '\\C-IS-TS-91\C$\Users\wframem\documents\WindowsPowerShell\Modules\MyModule'
  DeploymentType: Filesystem
    Mirror: True

We can verify how PSDeploy will parse this:


Let’s invoke the deployment:


If we check the destination paths, we find MyModule has been deployed!


What happened under the hood?

  • We read the deployments.yml
  • We see that the source is a folder
  • We see that the deployment type is Filesystem
  • We look up the script for filesystem deployments
  • We execute the filesystem script for this deployment
  • The script reads the deployment and processes it:
    • It’s a folder, so we use robocopy /E /XO
    • The ‘mirror’ deployment option is set, so we add on /PURGE

If you prefer illustrations, here’s quick diagram of a similar deployment:


That’s pretty much it! Instead of defining all this code in my continuous deployment system, I just run Invoke-PSDeployment.

Getting Started

First off, download and explore the module:

Running through the code above, we see a few commands you can work with:

  • Get-PSDeployment: Read a deployment config file
  • Get-PSDeploymentType: Get details on a deployment type
  • Get-PSDeploymentScript: Show available deployment types and associated scripts
  • Invoke-PSDeployment: Run a deployment

Let’s put this to work in a few real world scenarios.

Example Deployment Scenarios

Basic Filesystem Deployment

I want to deploy C:\Git\MyModuleRepo\MyModule to \\Server\Modules. How can I use PSDeploy to do this?

First, we add a config file, C:\Git\MyModuleRepo\deployments.yml. Here’s the content:

  Author: 'wframe'
    - 'MyModule'
    - '\\Server\Modules\MyModule'
  DeploymentType: Filesystem
    Mirror: True

We verify that the deployment will do what we want:

Looks good to me! In our continuous deployment solution, we simply run this:

    Invoke-PSDeployment -Path 'C:\Git\MyModuleRepo\deployments.yml'

This gist shows how you can test out PSDeploy without setting up any fancy tools. All you do is create a dummy folder to deploy, targets to deploy it to, and invoke the deployment.

Let’s look at a more involved deployment.

FilesystemRemote Deployment

So! I’m using Jenkins for continuous integration and trying to shoehorn in a deployment. I’m running into trouble: Jenkins is running with an account that doesn’t have the right privileges to deploy to the targets.

We can use the FilesystemRemote deployment type, which will run the robocopy and copy-item commands in a PowerShell remoting session with appropriate credentials. I know, it’s silly, pull requests for a more appropriate solution would be welcome : )

Ahead of time, I locked down the Jenkins server and set up EnvInject, with a little help from Matt Hodgkins’ post.

Next, I set up a project with the following PowerShell build command. I’m omitting the testing steps for simplicity. You should strongly consider gating your deployments using Pester tests.

This looks a bit complicated, but all we’re doing is loading up PSDeploy, grabbing credentials for our deployment, and kicking off the deployment with some parameters. You can run Get-Help Invoke-PSDeployment -Parameter DeploymentParameters for more information on passing parameters to a deployment script.

That’s about it! Maybe SomeSessionConfig is a delegated endpoint, which works around the double hop issue. I could also configure a CredSSP endpoint, but that might draw the wrath of the security community. Tip: Do consider the risks of CredSSP, but if you still have admins using RDP, you’re already using CredSSP, and you have bigger problems to worry about : )

We just walked through a few example deployment types. What if you want more deployment options?

Extending PSDeploy

I’m probably getting ahead of myself, but I tried to make PSDeploy somewhat extensible. Follow these steps to add a new deployment type:

  • Update PSDeploy.yml (Associates scripts to deployment types)
  • Write the deployment script
  • Consider whether and where to store options in the deployment config

Update PSDeploy.yml

This file is stored in the root of the module, although you can move it to a central spot. It stores the associations of deployment types to deployment scripts.

  • The deployment type is the root node.
  • The script node defines what script to run for these deployment types
  • The description is exposed when running Get-PSDeploymentType

For example, I might add support for SCP deployments:

  Script: SCP.ps1
  Description: Deploys artifacts using SCP. Requires Posh-SSH

Create the Deployment Script

These are stored in the PSDeploy folder under PSDeployScripts, although you can point to other paths in PSDeploy.yml

  • Following the SCP example, our deployment script might be C:\Path\To\PSDeploy\PSDeployScripts\SCP.ps1
  • In your deployment script (SCP.ps1), include a ‘Deployment’ parameter
  • Include comment based help to explain the deployment and any extra parameters you accept. This is a good spot to describe YAML options, if you use them.

Here’s how I implement the deployment parameter in the filesystem deployment scripts:

YAML Options

If you want to include options in a deployment config file, similar to the mirror option we use in Filesystem deployments, just include them in deployments.yml.

  • Get-PSDeployment converts the deployment config YAML and processes it into a number of ‘deployment’ objects that are passed to your script.
  • No special steps are needed for the deployment config, just add nodes as needed.
    • We extract the YAML Options node with Get-PSDeployment, and process it into a DeploymentOptions property on the Deployment object
    • If you need even more flexibility, we store the raw converted YAML in the Raw property returned from Get-PSDeployment

Here’s an example extended deployment config with a hostname and port option:

  Author: 'wframe'
    - 'SomeSourceFolder'
    - '/Some/Target/Path'
  DeploymentType: SCP
    Hostname: Server1
    Port: 22

We can explore this with Get-PSDeployment to see what we would work with in out deployment script:


Next Steps

That’s pretty much it! I’m hoping that we get more IT professionals using version control, continuous integration, continuous delivery, and similar solutions. We might see more (and better!) tools to simplify and streamline using these solutions as an IT professional.

Don’t be afraid of tools and ideas traditionally associated with developers or ‘DevOps’. Even if you work in an enterprise that has few if any developers, these tools and the ideas behind them can benefit IT as a whole.

If you haven’t already, you should consider joining the open source community. Suggestions, pull requests, and other contributions to PSDeploy would be more than welcome!