Expand-Tokens.ps1
function Expand-Tokens { [CmdletBinding()] param ( # Directory or file to do token replacement within. [Parameter(Mandatory = $true)] [string] $path, # Filenames (or patterns) to use for selecting files to replace tokens within, if the Path parameter is a directory. # Optional if the Path parameter already specifies a file. [Parameter(Mandatory = $false)] [string] $filenames, # Whether the search for files is recursive (defaults to no). Ignored if the Path parameter specifies a file. [Parameter(Mandatory = $false)] [switch] $recursive = $false, # Allows overriding the token start string, for custom file formats where the default is problematic. Regex syntax. [Parameter(Mandatory = $false)] [string] $tokenStart = "__", # Allows overriding the token end string, for custom file formats where the default is problematic. Regex syntax. [Parameter(Mandatory = $false)] [string] $tokenEnd = "__", # A list of secret-values variables ("key=value","key=value") to include in replacement. Secret build # process variables are not available for replacement without explicitly providing them here. [Parameter(Mandatory = $false)] [Alias("variables")] [string[]] $secrets ) # Forked from https://github.com/TotalALM/VSTS-Tasks/tree/master/Tasks/Tokenization and heavily modified. # Assemble all the secrets into a hashtable for easy use later. $secretValues = @{ } if ($secrets -and $secrets.Length -gt 0) { Write-Host "$($secrets.Length) secret variables provided on command line." foreach ($s in $secrets) { $pair = @($s -split "=", 2) if ($pair.Length -ne 2) { Write-Error "A secret value parameter was not formatted as key=value." } else { # We accept input from env variables. Under VSTS, this means . is converted to _ because # env variable names cannot contain dots. We want to be maximally compatible here, # accepting both _ and . in the secret variable names (always matching against _). $pair[0] = $pair[0] -replace "\.", "_" $secretValues[$pair[0]] = $pair[1] Write-Host "Obtained secret value with name $($pair[0])" } } } $tokenFindingRegex = $tokenStart + "[A-Za-z0-9._]+" + $tokenEnd Write-Host "Regex: $tokenFindingRegex" function ProcessFile($file) { $fileFullName = $file.FullName Write-Host "Found file: $fileFullName" $fileEncoding = Get-FileEncoding -path $fileFullName Write-Host "Detected file encoding: $fileEncoding" $newlines = Get-FileNewlineCharacters -path $fileFullName $matches = Select-String -Path $fileFullName -Pattern $tokenFindingRegex -AllMatches | ForEach-Object { $_.Matches } | ForEach-Object { $_.Value } if ($matches.Count -eq 0) { Write-Host "No placeholders in the file." return } $fileContent = Get-Content $fileFullName foreach ($match in $matches) { $matchedItem = $match $matchedItem = $matchedItem.TrimStart($tokenStart) $matchedItem = $matchedItem.TrimEnd($tokenEnd) Write-Host "Found token $matchedItem" -ForegroundColor Green # We accept input from env variables. Under VSTS, this means . is converted to _ because # env variable names cannot contain dots. We want to be maximally compatible here, # accepting both _ and . in the tokens (always matching against _). $matchedItem = $matchedItem -replace "\.", "_" if (Test-Path Env:$matchedItem) { $matchValue = (Get-ChildItem Env:$matchedItem).Value Write-Host "Found matching variable. Value: $matchValue" -ForegroundColor Green } elseif ($secretValues.ContainsKey($matchedItem)) { $matchValue = $secretValues[$matchedItem] Write-Host "Found matching secret variable." -ForegroundColor Green } else { $matchValue = "" Write-Host "Found no matching variable. Replaced with empty string." -ForegroundColor Green } $fileContent = $fileContent | ForEach-Object { $_ -replace $match, $matchValue } } $newContentAsString = [string]::Join($newlines, $fileContent) # If file is UTF-8 and has no BOM, make sure there is also no BOM in the newly created file. if ($fileEncoding -eq "UTF8") { if ($PSVersionTable.PSVersion.Major -le 5) { $bytes = Get-Content $fileFullName -Encoding Byte -TotalCount 3 } else { $bytes = Get-Content $fileFullName -AsByteStream -TotalCount 3 } if ($bytes[0] -eq 0xef -and $bytes[1] -eq 0xbb -and $bytes[2] -eq 0xbf) { Write-Verbose "Writing UTF8 file with BOM." Set-Content $fileFullName -Force -Encoding $fileEncoding -Value $newContentAsString -NoNewline } else { Write-Verbose "Writing UTF8 file without BOM." [IO.File]::WriteAllText($fileFullName, $newContentAsString) } } else { Write-Verbose "Writing $fileEncoding file." Set-Content $fileFullName -Force -Encoding $fileEncoding -Value $newContentAsString -NoNewline } } Write-Host "Target path: $path" Write-Host "Recursive: $recursive" if (Test-Path -PathType Leaf $path) { Write-Host "Target is a file. Will proceed directly to perform token replacement on it." Get-Item -Path $path | ForEach-Object { ProcessFile $_ } } elseif (Test-Path -PathType Container $path) { Write-Host "Target is a directory. Will replace tokens in contents." foreach ($target in $filenames.Split(",;".ToCharArray())) { Write-Host "Targeted filename or pattern: $target" if ($recursive) { Get-ChildItem -Path $path -File -Filter $target -Recurse | ForEach-Object { ProcessFile $_ } } else { Get-ChildItem -Path $path -File -Filter $target | ForEach-Object { ProcessFile $_ } } } } else { Write-Error "Target path points to item that does not exist." } } |