Working with Workday - The SOAP Opera (APIra?)

It’s been a couple of months and I’m back with new learning from the ever busy front where i’m at war with my own ignorance!

I’ve been having a look recently at quite a few web APIs (REST and SOAP and all that) and I’m here to share some slightly whacky odd script for getting Workday data from their SOAP API. For those that don’t know, Workday is a SaaS Human Capital Management (HCM) system with some grand ambitions around employee lifecycles, payroll, expenses and all those buzzwords and things you need to do when you employ people, pretty cool.

So looking at the Workday API pages, you might think SOAP? that’s old school!, use REST! For Workday, fortunately there is a REST API that’s easy to get to grips with, however it’s very difficult to get the security right and automate the login/authorization piece (at least with PowerShell, or my knowledge thereof). This means I’ve been concentrating on the SOAP API, which seems harder to set up the query, but a bit more usable from my position and security standpoint.

OK, so a SOAP API - New-WebServiceProxy to the rescue? Again, not much luck. For some reason I cannot get it to authenticate requests, so I had to fall back on good old Invoke-WebRequest for this one! The user account you use needs to be set up the same way as the system integration user defined here.

Essentially, I took a bunch of syntax for queries that already worked and amalgamated them in one string replacement hackfest. It’s not the cleanest piece of work in the world, but it gets the job done and allows me to keep trying New-WebServiceProxy in the background until I get that working. Any suggestions? Please let me know!

Here’s the function to get the data, you can delve into the returned object and get a load of good data out. I’ll clean up a function for exposing the object in a nice way too at some point soon, hopefully I can get to the point where I can release the entire module!

function GetWDWorkerData {
        A function to construct and send a SOAP request to Workday to retrieve worker data from the Workday API.

        A function to construct and send a SOAP request to Workday to retrieve worker data from the Workday API.

        .Parameter Credential
        The credentials required to access the Workday API.

        .Parameter RequestType
        The type of data you would like to retrieve. Defaults to returning all data.

        RequestType can be one of the following:


        .Parameter WorkerID
        Limit your search data to a single worker. If your request encompasses multiple workers, use the pipeline.

        By default all worker information is returned.

        .Parameter Tenant

        The tenant to query for information.

        GetWDWorker -Credential (Get-Credential) -Tenant TENANT -RequestType Contact

        Get contact data about all workers from the TENANT environment.

        Author: David Green
    Param (



        [ValidateScript( {
                $_ -lt 1000 -and $_ -gt 0
        $RecordsPerPage = 750,


    Process {
        $page = 0

        do {
            $Query = @{
                Uri             = "$Tenant/Human_Resources/v30.2"
                Method          = 'POST'
                UseBasicParsing = $true
                Body            = @"
<?xml version="1.0" encoding="utf-8"?>
        <wsse:Security env:mustUnderstand="1">
        <wd:Get_Workers_Request xmlns:wd="urn:com.workday/bsvc" wd:version="v30.2">
       $(if ($WorkerID) { @"
    <wd:Request_References wd:Skip_Non_Existing_Instances="true">
                    <wd:ID wd:type="Employee_ID">$($WorkerID)</wd:ID>
"@ })
                $(switch ($RequestType) {
                    'Contact' { "<wd:Include_Personal_Information>true</wd:Include_Personal_Information>" }
                    'Employment' { "<wd:Include_Employment_Information>true</wd:Include_Employment_Information>" }
                    'Management' { "<wd:Include_Management_Chain_Data>true</wd:Include_Management_Chain_Data>" }
                    'Organization' { "<wd:Include_Organizations>true</wd:Include_Organizations>" }
                    'Photo' { "<wd:Include_Photo>true</wd:Include_Photo>" }
                ContentType     = 'text/xml; charset=utf-8'

            [xml]$xmlresponse = Invoke-WebRequest @Query
            Write-Verbose -Message $Query.Uri

            if ($xmlresponse) {
                $ResultStatus = $xmlresponse.Envelope.Body.Get_Workers_Response.Response_Results
                [int]$Records += [int]$ResultStatus.Page_Results
                Write-Verbose -Message "$Records/$($ResultStatus.Total_Results) records retrieved."
                Write-Verbose -Message "$($ResultStatus.Page_Results) records this page ($($ResultStatus.Page)/$($ResultStatus.Total_Pages))."
                $TotalPages = $ResultStatus.Total_Pages
        while ($page -lt $TotalPages)

SCCM Cache size and client content management

Managing client content in SCCM is usually a zero touch affair. You set the content size and the data usually manages itself as it ages out over time and makes way for newer content. However, sometimes an application come along to break all those assumptions and make you cringe.

I recently had to deploy an application that required me to do just that. Two halves of an application, with the larger piece being a svelte 20GB of install content, even when compressed!

This required me to do two things to ensure the SCCM clients wouldn’t run into too many problems, especially if they’d been recently deployed.

One thing was to force clear the client cache before install, to use the minimal amount of space and free up space that may have been used for deployments that day, especially if the machine was freshly deployed, whee this is usually an issue. The second thing was to temporarily increase the cache size for the install with a silent required application, then reset/leave it to the SCCM policy afterward, since these apps are very rare.

For both of these things, I used my trusty friend PowerShell to load the SCCM client management COM object, then expand or clear the client content. Big thanks to both OzThe2 and kittiah on reddit for the info, near enough just altered things into functions to make my deployment tidier.

Here’s the clean cache script merged into the function. For the cache size script, see here:

#Requires -RunAsAdministrator

Param (
    $OlderThan = 30

function Clear-CCMCache {
            Clears the content from the CCMCache.

            Clears the content from the CCMCache.
            If no parameters are supplied then the cache is completely cleaned.

            To use this in Configuration Manager, on the Programs tab on the Deployment Type, set the 'Program' to be:

            Powershell -ExecutionPolicy Bypass -File .\Clear-CCMCache.ps1 -OlderThan 30

            The number after -OlderThan reflects the age of the CCM cache content to clear.
            If you leave out the -OlderThan parameter then all cache will be cleared.

            Clear-CCMCache -OlderThan 2

            Clears the cache data older than 2 days

            Author: David Green

    Param (

    if ($OlderThan) {
        $TargetDate = (Get-Date).AddDays($OlderThan * -1)
    else {
        $TargetDate = Get-Date

    # Create Cache COM object
    $CCM = New-Object -ComObject UIResource.UIResourceMgr
    $CCMCache = $CCM.GetCacheInfo()

    # Enumerate all cache items, remove any not referenced since $targetDate from the CCM Cache
    $CCMCache.GetCacheElements() | ForEach-Object {
        if ($_.LastReferenceTime -lt $TargetDate) {

    Write-Verbose -Message 'Updating registry.'

    # Create the registry key we will use for checking last run.
    $RegistryKey = 'HKLM:\System\CCMCache'
    New-Item -Path $RegistryKey -Force

    # Add size that CCMCache was changed to in MB
    $Item = @{
        Path         = $RegistryKey
        Name         = 'LastCleared'
        Value        = (Get-Date -Format 'dd/MM/yyyy HH:mm:ss').ToString()
        PropertyType = 'String'
        Force        = $true

    New-ItemProperty @Item

    Write-Verbose -Message 'Done.'

# Script entry point
Clear-CCMCache -OlderThan $OlderThan

PowerShell Modules - Storing state for connections

Following on from my recent module writing work, i’ve been using a little trick in some of my PowerShell modules to store a small amount of state that persists as long as the module is loaded. I’ve been primarily using this strategy as a way to manage connections for SharePoint and REST services.

My example here is a small one derived loosely from a SharePoint Client-Side Object Model (CSOM) module I use and maintain. Essentially, in the module .psm1 file the variable is defined and then used by a couple of functions to set, get and clear the connection data using the $script:variable scoping, as shown below:

Note: You need the SharePoint Online PowerShell module for the SharePoint DLLs!

$Assemblies = @(
    "$env:ProgramFiles\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.SharePoint.Client.dll"
    "$env:ProgramFiles\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.SharePoint.Client.Runtime.dll"
    "$env:ProgramFiles\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.Online.SharePoint.Client.Tenant.dll"

Add-Type -Path $Assemblies -Verbose
# SharePoint connection states
[System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '')]
$Connection = New-Object System.Collections.ArrayList

Function Connect-SharePoint {
    Param (



    # Login
    $SPContext = New-Object Microsoft.SharePoint.Client.ClientContext($Uri)
    $SPContext.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials(

    try {
        $null = $Script:Connection.Add($SPContext)

        if ($PassThru) {
    catch {
        Write-Error "Error connecting to SharePoint."

Function Get-SharePointConnection {
    Param (

    if ($Site) {
        $Script:Connection | Where-Object -Property URL -eq -Value $Site
    else {

Function Disconnct-SharePoint {
    [CmdletBinding(DefaultParameterSetName = 'Site')]
    Param (
            ParameterSetName = 'Site'

            ParameterSetName = 'All'

    if ($PSCmdlet.ParameterSetName -eq 'Site') {
        $Disconnect = Get-SharePointConnection -Site $Site

        if ($Disconnect) {
        else {
            throw "No connection for site $Site found."

    if ($PSCmdlet.ParameterSetName -eq 'All') {
        $Script:Connection = New-Object System.Collections.ArrayList

Anyways, hopefully this helps for managing connections, REST auth tokens and a few other little scenarios. I’d be wary of using this approach too often for things like modifying function or module states, as it’s not really the one function for a job (the UNIX way) most PowerShell users might expect.

Using VSTS for building and hosting PowerShell modules with Package Management

I’ve been working with VSTS a lot recently as a source management solution. Rather than build and distribute my modules through a file share or some other weird and wacky way, I thought i’d try to use VSTS Package Management to run myself a PSGallery-alike, without running PSGallery! This is related to another coming-soon blog post about building and running a cloud platform business rule compliance testing solution using the VSTS hosted build runner too.

All of this was to work towards to run things in a little more of a continuous integration (CI) friendly way, hopefully making a start towards a release pipeline model for cloud service configurations.

Getting Started

So i started with this great guide on how to do it manually, creating pakages and pushing them to VSTS Package Management. This works great, but I wanted to close the loop by automatically building my module, along with packing and pushing my nuget packages to VSTS. Here’s how I did it…

Generating the module manifest and nuspec file

You don’t have to generate both automagically, I suppose you could generate the nuspec from the module manifest, which would be quite straightforward. My way is not the only way :) Anyways, based on all the reorganising I do of my modules, it’s easier to generate both so I don’t have to worry about the module manifest file list or the cmdlets/functions/aliases to export.

I use the a project structure like the one used in the Plaster module (since that’s usually my starting point!). This means that a rough module structure looks like this:

|-\release (.gitignored)
| |-Module.nuspec (.gitignored)
| |-Module.psd1   (.gitignored)
| |-Module.psm1
| |-testfile.test.ps1

As you can see, most of those files look pretty much identical to those you get from the Plaster NewModule example with one or two exceptions. The major one is build.manifest.ps1 which is the script I use to build the manifest and stitch together all the pieces defining the module. This script gets called as part of the build and creates two files shown in the above structure, Module.nuspec and Module.psd1.

Here’s the content of this file, I’ve also used it in an example module hosted in GitHub here.

$ModuleName = (Get-Item -Path $PSScriptRoot).Name
$ModuleRoot = "$PSScriptRoot\src\$ModuleName.psm1"

# Removes all versions of the module from the session before importing
Get-Module $ModuleName | Remove-Module
$Module         = Import-Module $ModuleRoot -PassThru -ErrorAction Stop
$ModuleCommands = Get-Command -Module $Module
Remove-Module $Module

if ($ModuleCommands) {
    $Function = $ModuleCommands | Where-Object { $_.CommandType -eq 'Function' -and $_.Name -like '*-*' }
    $Cmdlet = $ModuleCommands   | Where-Object { $_.CommandType -eq 'Cmdlet' -and $_.Name -like '*-*' }
    $Alias = $ModuleCommands    | Where-Object { $_.CommandType -eq 'Alias' -and $_.Name -like '*-*' }

Push-Location -Path $PSScriptRoot\src
$FileList = (Get-ChildItem -Recurse | Resolve-Path -Relative).Substring(2) | Where-Object { $_ -like '*.*' }

$ModuleDescription = @{
    Path                = "$(Split-Path -Path $ModuleRoot)\$((Get-Item -Path $ModuleRoot).BaseName).psd1"
    Description         = 'A PowerShell script module.'
    RootModule          = "$ModuleName.psm1"
    Author              = 'David Green'
    CompanyName         = ''
    Copyright           = '(c) 2018. All rights reserved.'
    PowerShellVersion   = '5.1'
    ModuleVersion       = '1.0.0'
    # RequiredModules   = ''
    FileList            = $FileList
    FunctionsToExport   = $Function
    CmdletsToExport     = $Cmdlet
    AliasesToExport     = $Alias
    Tags                = $ModuleName
    # VariablesToExport = ''
    # LicenseUri        = ''
    # ProjectUri        = ''
    # IconUri           = ''
    ReleaseNotes        = Get-Content -Path "$PSScriptRoot\" -Raw

[string]$Tags = $ModuleDescription.Tags | Foreach-Object { "'$_' " }

[xml]$ModuleNuspec = @"
<?xml version="1.0"?>
    <releaseNotes>$(Get-Content -Path "$PSScriptRoot\" -Raw)</releaseNotes>

New-ModuleManifest @ModuleDescription
$ModuleNuspec.Save("$(Split-Path -Path $ModuleRoot)\$((Get-Item -Path $ModuleRoot).BaseName).nuspec")

The build script

This next small example is from build.ps1, showing where the manifest build is called, along with pre-installing any prerequisites needed, as generating the manifest requires me to import the module. I don’t currently alter the build.psake.ps1 file, as I want to be able to easily consume any updates to the psake script (although it should really be in the build.settings.ps1 in that case!).


Param (
    $Task = 'build',



$psake = @{
    buildFile  = "$PSScriptRoot\Build.psake.ps1"
    taskList   = $Task
    Verbose    = $VerbosePreference

if ($Parameters) {
    $psake.parameters = $Parameters

# Prerequisites
if ($InstallPrerequisites) {
    if (-not (Get-PackageProvider -Name NuGet -ErrorAction SilentlyContinue)) {
        Install-PackageProvider -Name NuGet -Force -Scope CurrentUser

    'pester', 'psake' | ForEach-Object {
        Install-Module -Name $_ -Force -Verbose -Scope CurrentUser -SkipPublisherCheck

. $PSScriptRoot\Build.manifest.ps1
Invoke-psake @psake

OK! so hopefully you can implement this file to build your manifest and nuspec file, so you could run your nuget pack and nuget push, then call it a day, right? Kind of… But wouldn’t it be easier for someone else to do the pack and push? Enter VSTS Package Management! You’ve been committing all this to source control right?!

Building the steps for ‘pack and push’

To build the steps for pack and push, you need to have the following prerequisites in place (i’m assuming you’ve already got git, or the first half of this post may have missed the mark):

Once you’ve got those installed and read through the getting started with VSTS stuff to get a good grounding in what it’s all about, We can push the code to the remote origin, then we can build… the build!

We can add the remote git server with:

git remote add <remote name> <repository url>

Then initialise the empty remote server with all our content and history using:

git push -u <remote name> -all

Now we should have our module in VSTS, looking a little lonely, just waiting to get built! We can go to Build and Release > Packages to start the process, but we might need to add an extension license before we do that.

Package Management

Click your user, then click Manage extensions and apply the Package Management license. After that, we can go back to the Packages page to create the new feed, here’s a screenshot.

Create a new feed

Now we can navigate to Builds and click + New definition to create a build definition. We’ll use VSTS Git and use an empty build definition.

Build steps and script

We’ll create three steps, a simple build for the module! We’re runing build.ps1, which will install the prerequisites like Pester and psake if needed, which for the VSTS build runner, we will require. Then we can build the module into the Release folder and give it a good test before we give it to nuget to pick up in the next step.

Nuget pack

This step is almost entirely as-is, just pointing to the expected path of the Example2.nuspec file to grab and pack up.

Nuget pack

This step again is almost-default and just points to the target feed, nothing else has been changed. You could collect the test results as well, along with some other things to make sure you’ve collected all the good data about the build, but that’s outside the scope of this post.

So now we’ve got a module, it’s built, it’s tested, it’s packed and pushed and ready to go! So how do we use it?

Installing modules from the feed

Remember the package feed we created earlier? Now the feed is created and we’ve pushed a package to it, it’s got a use! If you navigate back to the Build and Release > Packages, then click Connect to feed, you can then copy the Package source URL, changing nuget/v3 to nuget/v2 which will look something like this:


This is the Repository URL for the PSRepository we’ll want to register, the only other detail we need will be the Personal Access Token (PAT), details of how to generate one can be found here (accessible through your profile security page).

Basically you an use anything for your username, as long as the password is your PAT, it’ll connect and let you seach, download and install packages from this feed.

$Cred = Get-Credential

$Splat = @{
    Name               = 'PowerShell'
    InstallationPolicy = 'Trusted'
    SourceLocation     = 'https://<VSTSName><TeamName>/nuget/v2'
    Verbose            = $true
    Credential         = $Cred

Register-PSRepository @Splat

Install-Module -Repository PowerShell -Name Example2 -Scope CurrentUser -Credential $Cred -Verbose

Final thoughts

The only issue I currently have is that since the package feed is immutable, you have to make sure you have upped the module version in order to get the push to work, or you end up with a build that works, but an error at the end like:

Error: An unexpected error occurred while trying to push the package with VstsNuGetPush.exe. Exit code(2) and error(The feed already contains ‘Example2 1.0.0’.)

I’m unsure how to get my CI process (build + test) running automatically, while also having a build test and release working automatically when I bump the version number. I’ll continue to discover new things though and post once I figure it out! Alternatively, if you already know how, please share! :)

And that’s it! Hopefully this give you some good information on working with your own PowerShell module CI solutions within VSTS and Package Management.

Getting Started with custom Docker containers on Azure

This post is something a little bit different to what I normally post (Hint: Linux :)), but I’m not a platform snob, you’ve got to use the best tool for the job!

In this case, the customer requirements were to run a PHP5 web application in Apache, with a MySQL backend, plus a few PHP modules and PhantomJS for charting, so Linux it is.

Before I got involved, the plan was for a two-tier architecture hosted using Azure IaaS VMs. From a customer point of view, this is a reasonably expensive solution, as there is a fair bit of intervention required for maintenance and backups, etc. which would inflate the cost. This solution also falls into a bit of a cost pit from Azure availability recommendations, which would require at least 4 VMs, plus load balancing get deployed, making the solution even more expensive.

Essentially, it sounded like a good time to sprinkle some cloud magic on it and see where the infrastructure ended up!

All of the command lines I use in this example are right to the best of my ability, but please let me know if there are any issues!

Initial Idea

I’d heard about Docker containers a bit, from a few people and I knew it would be a good fit for the web frontend for this (assuming I could get it working) and although this would be my first proper foray into using Docker for a production service, I was reasonably confident this would be the way forward. I could always fall back on the old plan, right?

That covers the web frontend you say, but what about the database! For the DB I noticed this little gem appear not so long ago in Azure, so that’s my target for that sorted too! (Yes I know it’s preview, but the roadmap points to early 2018 as GA. This app won’t be in production before then).

Let’s play… with Docker containers!

The very first step is running up my test machine. Since this is going to be a production service (eventually), my Linux distribution of choice is Debian. You may have a different take on it, but when it’s got to be Linux and work in a production environment, I’ve yet to have a better experience.

Once I’ve got everything installed (Git, Docker-CE, etc.), it’s time to get an initial PHP web app up to test. Since my plan was to deploy a custom Docker image, rather than reinvent the wheel, I wanted to find a base Apache/PHP5 docker image that I could test on Azure, before adding PhantomJS. Luckily, I found the ideal Docker image to start with in the Azure-App-Service/PHP repository (5.6.21-apache).

Note: If you’re trying to install the Azure CLI tools but getting odd failures, make sure to install python-pip first. The install script doesn’t always seem to catch the dependency being missing, but this fixed it for me.

I started testing this docker image with a test PHP file I could include, rather than hostingstart.html, other than that, I left the Dockerfile as default (for now).

COPY hostingstart.html /home/site/wwwroot/hostingstart.html
+COPY success.php /home/site/wwwroot/success.php

RUN a2enmod rewrite expires include deflate

To run the container, we then just use the standard build and run commands, making sure to map the Apache port through to the host machine.

> docker build -t container .

> docker run -d -p 8080:8080 container

Included test page

Included test page

And just for completeness, my PHP test page

PHP test page

Getting PhantomJS in the container

Update: This package is broken and seems to require bits of QT to function correctly. I have edited the post to install the PhantomJS package from binaries provided by ariya

Because the requirements of the web app only need the PhantomJS binary and nothing else fancy (like a listen port), we don’t need to set up a separate container for PhantomJS, we can just install the package into the container to be used directly by the web application. The largest problem with this, is that the docker image is based on Debian version 8 (Jessie), rather than the current stable release (version 9, ‘Stretch’). The PhantomJS package is only available in jessie-backports, whereas in stable, it’s in the main package list.

To get this working initially, this led me to edit the package install RUN line to add the backports repository and make sure that PhantomJS is installed. Unfortunately, this did not work as the package has some issues, so I had to install it from another source.

This changes the docker file, but only adds a few different RUN lines, rather than editing too much.

+RUN { \
+        cd /root; \
+        wget; \
+        tar xvjf phantomjs-2.1.1-linux-x86_64.tar.bz2; \
+        cp phantomjs-2.1.1-linux-x86_64/bin/phantomjs /usr/local/bin/phantomjs; \
+        ln -sf /usr/local/bin/phantomjs /usr/bin/phantomjs; \
+    }

Now I can build the resulting container and test locally, by pushing things into /home/site/wwwroot and as if by magic, it works with either a local or Azure MySQL instance (once I’d added my public IP to the access list :P).

Build and publish to an Azure Container Registry

I needed a place to store the resulting container artifact, I decided to use an Azure Container Registry for this. I could have used the Docker Hub, but I thought it would be better to keep things in one place.

To deploy the container registry, i ran:

> az acr create -n containerregistry -g weblinuxcontainer --sku Basic --admin-enabled true

> az acr credential show -n containerregistry

I navigated to the Dockerfile folder for my custom container and ran this:

> docker build -t container .

> docker login --username containerregistry --password <password from acr credential>

> docker tag container:latest

> docker push

Testing it all out

To test and use this all in Azure we’ve just got to build the Linux Web App Service plan and create the Web App inside it. Once this is done, we can hook up the Azure container registry to the deployed Web App and then hopefully end up with a web page, or two!

> az appservice plan create -g weblinuxcontainer -n weblinuxplan --is-linux --sku B1

> az webapp create -g weblinuxcontainer -p weblinuxplan -n weblinuxapp

> az webapp config container set --name weblinuxapp --resource-group weblinuxcontainer --docker-custom-image-name container:v1 --docker-registry-server-url --docker-registry-server-user containerregistryuser --docker-registry-server-password <container registry password>

The Azure container service should then figure out the website port automagically, if it doesn’t you will need to tell Azure, you can do this with:

> az webapp config appsettings set --resource-group weblinuxcontainer --name weblinuxapp --settings WEBSITES_PORT=8080

You can then imagine how easy it is to start hooking in Azure MySQL, or another database as the data layer to this app. If your application requires some data to be available to the container, for test purposes or some legacy application storge in an ‘uploads’ folder there is always the ability to use the app service storage account to keep state within the scale set.

You will need to upload the files over FTP to this storage account. If you do this in production, remember to set backups!

> az webapp config container set --name weblinuxapp --resource-group weblinuxcontainer --enable-app-service-storage true

Final thoughts

Because I am using the built in app service storage account, I tried getting some continuous integration magic working by setting up a trigger on commit for Visual Studio Team Services to FTP files to the Web App storage to set a really nice development cycle up and running.

Visual Studio Team Services Error

This worked… but only with small amounts of files. Once there is over 300 files in the folder structure, I see connection failures start to plague the test deployment, which is annoying! I tried a few things, but haven’t managed to get past this yet. I’ll push on with this and see where I get, there’s a couple more ideas I have in mind to perhaps make this better.

Hopefully this was a nice intro into using custom Docker containers on Azure. It will definitely stay as a good set of notes for me for a while :)