Web service endpoint URL replacement in .config files

We are heavily leaning towards using the “configuration variables” instead of transforms.

The appSettings and connectionStrings sections are easy – however, I’m not seeing an out-of-the-box way to replace web service endpoint point addresses. I saw some older posts but wasn’t sure if a custom PowerShell script is still the way to do this.

Basically, I need the endpoint address replacement to work like the appSetting and connectionString sections: OD variable name matched on the unique endpoint name, and the address is the value, and scope is applied.

Does this exist? Or is there a Powershell script out there that does it already?


Hi Tom,

Thanks for getting in touch! There are three ways to replace variables in a config for anything outside of appSettings and connectionStrings.
Those are:
Use the Substitute variables in files feature and hard code the values to replace with Octopus Variables.
Use this library script https://library.octopusdeploy.com/#!/step-template/actiontemplate-file-system-find-and-replace
Or create a powershell script.

There are pros and cons to each. Some customers don’t like the first because it breaks the config locally.

Ok. I wound up creating a Powershell module that I can reference in my process steps:

function Replace-Endpoint-Addresses($varPrefix) {

    $installPath = if ([string]::IsNullOrEmpty($OctopusParameters["Octopus.Action.Package.CustomInstallationDirectory"])) { $OctopusParameters["OctopusOriginalPackageDirectoryPath"] } else { $OctopusParameters["Octopus.Action.Package.CustomInstallationDirectory"] }

    $search = $($installPath + "\**\*.config")

    $glob = $search.Trim()
    Write-Output "Searching for files that match $glob..."

    $matches = $null
    $splits = $glob.Split(@('/**/'), [System.StringSplitOptions]::RemoveEmptyEntries)

    if ($splits.Length -eq 1) {
        $splits = $glob.Split(@('\**\'), [System.StringSplitOptions]::RemoveEmptyEntries)
    if ($splits.Length -eq 1) {
        $matches = ls $glob
    } else {
        if ($splits.Length -eq 2) {
            pushd $splits[0]
            $matches = ls $splits[1] -Recurse
        } else {
            throw "The segment '**' can only appear once, as a directory name, in the glob expression"


    $matches | foreach {
        $target = $_.FullName
        Write-Output "Will replace endpoints in $target"
        Do-Endpoint-Replace $target $varPrefix


function Do-Endpoint-Replace($targetFile, $varPrefix) {
    $doSave = $FALSE
    $configXml = [xml](Get-Content $targetFile)

    $endpoints = $configXml.selectNodes("//system.serviceModel/client/endpoint")

    foreach ($endpoint in $endpoints) {
        $endpointName = ($endpoint.attributes | where {$_.Name -eq "name"}).value
        $varName = if([string]::IsNullOrEmpty($varPrefix)) { $endpointName } else { "$varPrefix.$endpointName" }

        if ($OctopusParameters.ContainsKey($varName)) {
            Write-Output $("Setting address for $endpointName to " + $OctopusParameters[$varName])
            ($endpoint.attributes | where {$_.Name -eq "address"}).value = $OctopusParameters[$varName]
            $doSave = $TRUE


    #Write-Output $configXml.OuterXml
    if ($doSave) { 

This is a pretty common config tweak, I wound think. May want to consider adding this as a feature in future versions!

Thanks again