Skip to content

Automating with Jenkins and PowerShell on Windows - Part 2

· 10 min

After reading Automating with Jenkins and PowerShell on Windows Part - 1, you should have a grasp on the basics of Jenkins and be excited to start doing more automation!

Let’s start reaching out into our network with Jenkins and take actions on remote machines.

Jenkins provides a means to do this, which is to install a Jenkins agent onto each machine you want to reach out to. This is a decent option, but instead lets use PowerShell’s built-in remoting features. This will save us having to install agents on all our remote systems and means we can keep complexity down.

If you are new to PowerShell remoting, you will be able to follow along, but I recommend reading the free eBook Secrets of PowerShell Remoting to get up to speed.

📢 Want to be notified when I post more like this? Follow me on Twitter: @MattHodge 📢

Using SSL on the Jenkins Web Interface#

As we will be working with credentials inside Jenkins, first let’s beef up security by installing a SSL certificate on the Jenkins web interface.

We will use OpenSSL to generate a self-signed SSL certificate, preventing any passwords entered into the Jenkins web interface from going over the network in plain text.

Create-Certificate.ps1
# Enter the bin directory inside the OpenSSL install folder
cd C:\OpenSSL-Win64\bin
# Generate a self-signed SSL certificate. Be sure to update your paths if required.
openssl req -newkey rsa:4096 -nodes -sha256 -keyout C:\temp\jenkins.key -x509 -days 365 -out C:\temp\jenkins.crt -config C:\OpenSSL-Win64\bin\openssl.cfg
Terminal window
# Stop the Jenkins Server
Stop-Service -Name Jenkins
# Edit the Jenkins configuration file
notepad 'C:\Program Files (x86)\Jenkins\jenkins.xml'
<arguments>-Xrs -Xmx256m -Dhudson.lifecycle=hudson.lifecycle.WindowsServiceLifecycle -jar "%BASE%\jenkins.war" --httpPort=8080</arguments>
<arguments>-Xrs -Xmx256m -Dhudson.lifecycle=hudson.lifecycle.WindowsServiceLifecycle -jar "%BASE%\jenkins.war" --httpPort=-1 --httpsPort=443 --httpsCertificate=C:/ssl/jenkins.crt --httpsPrivateKey=C:/ssl/jenkins.key</arguments>
Terminal window
# Start the Jenkins Server
Start-Service -Name Jenkins

From your browser, hit the Jenkins web interface on the port you specified and you will be in business. (Remember to put https:// in front!)

Configure the Jenkins Server for Remoting and Script Execution#

Next up, we need to allow the Jenkins server to access machines on the network via PowerShell Remoting.

To do this, we need add the hosts we plan remotely managing to the WS-Man trusted host lists. The method you choose will depend on your environment.

Terminal window
# Get a list of trusted hosts
Get-Item WSMan:\localhost\Client\TrustedHosts
# Note that these commands don't create a list of trusted hosts, it simply replaces the trusted host with what you set via the command. If you need to add multiple hosts, they need to be comma seperated
# Trust all computers in a domain
Set-Item WSMan:\localhost\Client\TrustedHosts *.contoso.com
# Turst a single machine
Set-Item WSMan:\localhost\Client\TrustedHosts -Value myserver
# Add another single machine
$trustedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts).value
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "$trustedHosts, mynextserver"
# Trust an IP address range
Set-Item WSMan:\localhost\Client\TrustedHosts -value 192.168.10.*
# Trust all remote machines (not recommended)
Set-Item WSMan:\localhost\Client\TrustedHosts *

We also may want to have Jenkins execute PowerShell script files, so we will set the PowerShell execution policy of the Jenkins server. We will configure both the x64 and x86 execution policies.

Set PowerShell Execution Policy PowerShell#

We will set the execution policy for x64 and x86 PowerShell

x86#

Terminal window
Set-ExecutionPolicy RemoteSigned -Force

x64#

Terminal window
Set-ExecutionPolicy RemoteSigned -Force

Install a new Plugin#

We will install the EnvInject Plugin which allow us to inject some stored environment variables into our build (including passwords).

Passing Credentials to PowerShell Jobs#

There are two ways that you can hand credentials to jobs in Jenkins

Parameterizing a Jenkins Job with Credentials#

In this example, we are going restart a service on a remote machine.

Tip

Remember that Jenkins makes the parameters available using environment variables. ComputerName and Username already exists as environment variables, so I am not using the standard naming convention for the parameters you would use inside PowerShell.

Terminal window
# Ensure the build fails if there is a problem.
# The build will fail if there are any errors on the remote machine too!
$ErrorActionPreference = 'Stop'
# Create a PSCredential Object using the "User" and "Password" parameters that you passed to the job
$SecurePassword = $env:Password | ConvertTo-SecureString -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential -ArgumentList $env:User, $SecurePassword
# Invoke a command on the remote machine.
# It depends on the type of job you are executing on the remote machine as to if you want to use "-ErrorAction Stop" on your Invoke-Command.
Invoke-Command -ComputerName $env:Computer -Credential $cred -ScriptBlock {
# Restart the W32Time service
Restart-Service -Name W32Time
}
Tip

If you are using a domain account to access the machine, use DOMAIN\YourUserName. If you are using a non-domain account as your username, in the User box put \YourUserName. Entering it in without the leading backslash will cause the job to fail.

Storing Credentials in Jenkins#

In this example, we are going to make a job that creates a text on a remote machine. We don’t want to have to enter our username and password each time we run a job, so we will store our credentials in Jenkins and use them in our PowerShell job.

There will be two parameters - one for name of the text file and one for its contents. Normally you wouldn’t have parameters on a job where you are pulling credentials with Jenkins, which would allow the job to run without intervention. In our case we are doing it so we can see how to pass multiple Jenkins parameters into a remote PowerShell session.

Tip

A best practice would be to create a dedicated service account for performing the build. Using your own credentials isn’t a great idea as your password can change. Additionally, your account may have far more privileges than are needed to do a simple remote task, which is a bad security practice.

Jenkins Global Passwords

Jenkins Global Passwords

Now that the password is now stored in Jenkins, we will create the build.

Jenkins Inject Passwords as Environment Variables

Jenkins Inject Passwords as Environment Variables
Terminal window
# Ensure the build fails if there is a problem.
# The build will fail if there are any errors on the remote machine too!
$ErrorActionPreference = 'Stop'
# Create a password variable using the stored password from the Jenkins Global Passwords
$SecurePassword = $env:PasswordForHodge | ConvertTo-SecureString -AsPlainText -Force
# If remoting to a machine with domain credentials, use "DOMAIN\YourUserName"
# If remoting to a machine on a workgroup, use "\YourUserName"
$User = "\Hodge"
# Create a PSCredential Object using the a hardcorded username and and $SecurePassword variable
$cred = New-Object System.Management.Automation.PSCredential -ArgumentList $User, $SecurePassword
# Build an object to store variables to send remotley
$objForRemote = @{}
# Add build paramters to the object
$objForRemote.FileName = $env:FileName
$objForRemote.FileContent = $env:FileContent
# (Optional) I like to output the $objForRemote variable so I can confirm by looking at the Jenkins Console Output of the build I set my parameters correctly.
Write-Output $objForRemote
# Invoke a command on the remote machine, sending $objForRemote to the ArgumentList paramater.
# It depends on the type of job you are executing on the remote machine as to if you want to use "-ErrorAction Stop" on your Invoke-Command.
Invoke-Command -ComputerName $env:Computer -Credential $cred -ArgumentList $objForRemote -ScriptBlock {
# Get the arguments passed to the remote session into the same variable name for ease of use
$objForRemote = $args[0]
# Create Temp Directory
if (-not(Test-Path -Path 'C:\temp'))
{
New-Item -Path 'C:\temp' -ItemType directory
}
# Using the environment variables exposed by the Jenkins job
Set-Content -Path "C:\temp\$($objForRemote.FileName).txt" -Value $objForRemote.FileContent
}

Run the build#

From the Jenkins main screen, run the Create Text File Remotely build and enter the parameters.

Run Build

Run Build

Verify the build was successful and take a look on the remote system - the file should be there!

Verify Build

Verify Build

Conclusion#

The builds and scripts above will give you a good framework for creating and PowerShell Remoting jobs with remoting. The builds above as they are not overly useful, but they provide the building blocks needed to do some awesome automation in your environment.