1

I am creating a text file template for Arista switch configs, and I just worked out how to replace variables in my templates using Invoke-Expression.

The existing solution works fine for singular variables, but I have arrays I'd like to use as well, and later on, I will have some ForEach loops.

I need to know how to get this method to actually perform the operations inside my template, or alternatively, edit the 'invoked expression' before dumping it to a file.

Current script snippet :

#Variables
$peerlink1int = "49"
$stamps = @("208","209","210","211")

$filename = ('SwitchConfig' + $(get-date -f 'MM-dd-yyyy HH-mm'))
$destination_file = ".\$filename.txt"

$Base = Get-Content '.\Arista\01AristaBase.txt' | out-string
$revisedBase = Invoke-Expression "`"$Base`""
$revisedBase | Out-File -append $destination_file

Source file snippet :

interface Ethernet $peerlink1int
   switchport trunk allowed vlan $Stamps[0]-$Stamps[-1],1111,1234

Output result :

interface Ethernet 49
   switchport trunk allowed vlan 208 209 210 212[0]-208 209 210 211[-1],1111,1234

What I expected :

interface Ethernet 49
   switchport trunk allowed vlan 208-211,1111,1234

EDIT : As an aside, there was another method proposed to me that did not use Invoke-Expression. (Everywhere I go someone says it's unsafe to use.) However, this other method merely replaces text. I need a method of editing a text template that allows me to perform some commands on the resulting text.

For example, I need a ForEach loop that takes a template text of :

interface Vlan$stamps
   description V$stamps
   ip address 192.168.$stamps.2/25
   vrrp $stampnum ip 192.168.$stamps.1
   vrrp $stampnum description Stamp$stamps

And loops one time for every item in the array $stamps so you would get :

interface Vlan208
   description V208
   ip address 192.168.208.2/25
   vrrp $stampnum ip 192.168.208.1
   vrrp $stampnum description Stamp208

interface Vlan209
   description V209
   ip address 192.168.209.2/25
   vrrp $stampnum ip 192.168.209.1
   vrrp $stampnum description Stamp209

interface Vlan210
   description V210
   ip address 192.168.210.2/25
   vrrp $stampnum ip 192.168.210.1
   vrrp $stampnum description Stamp210

interface Vlan211
   description V211
   ip address 192.168.211.2/25
   vrrp $stampnum ip 192.168.211.1
   vrrp $stampnum description Stamp211
n0ttsweet
  • 69
  • 1
  • 7
  • I am not sure where the problem is exactly. Do you have a particular question, if possible also with an error message? – Alex_P Jan 23 '20 at 21:52
  • Look at the "output result" vs "what I expected" The file 01AristaBase.txt is where the source file snippet comes from, Invoke-Expression does not respect the array [0] and [-1]. It just prints the whole array twice. – n0ttsweet Jan 23 '20 at 21:59
  • 1
    Here is the caveat to what you are doing. You are reading in a string and running invoke-expression on it. You can read in variables but [x] is considered part of the string, not variable. Unless you can do $($stamps[0]) in the text file, it wont work with invoke-expression – Jawad Jan 23 '20 at 22:16
  • That is what seemed to be the case. I think that perhaps "invoke-expression" as well as the original replacement method is not going to work for what my end goal is. My objective is to create 5 or 6 config template sections that contain mostly plaintext, but a few variables as well. Then I want to either replace the variables with new values or do something like the foreach loop above. I am wondering if I should just make a bunch of small .ps1 files and use "echo" instead?? – n0ttsweet Jan 23 '20 at 22:19
  • @Jawad Negative index DOES work... try this in PS: `$stamps = @("208","209","210","212")` `$stamps[-1]` ` – n0ttsweet Jan 23 '20 at 22:20
  • 1
    I, honestly work with various template / ini files that have to be updated based on certain requirements. I use in the template file and replace them with -replace. Not suitable for large content but works for small sized files I have – Jawad Jan 23 '20 at 22:22
  • -replace will work for "value to value" replacements. However, I also need to perform some ForEach loops on some sections of text. If I have an array of 5 'stamps' I need to create 5 vlans for them. etc – n0ttsweet Jan 23 '20 at 22:23
  • I'll write something up that you can use in a loop as wrll.. away from desk at the moment but I am sure someone will suggest a better solution – Jawad Jan 23 '20 at 22:26
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/206538/discussion-between-n0ttsweet-and-jawad). – n0ttsweet Jan 23 '20 at 22:26

2 Answers2

1

This is what I would recommend for different scenarios that you have.

NOTE: You should not use Invoke-Expression but I wanted to pass out the information based on your previous request where you wanted to see how it would work if you didnt use -replace. I am providing with -replace which should be considered before you use Invoke-Expression.

1. Replace two indexed items

You can replace the two indexed items using -replace method on a string. For these examples, you can read the data from File by using Get-Content and when you do, please make sure you pipe that to Out-String. Otherwise, the newline and other special characters seem to disappear.

$stampArray = @("208","209","210","211")
$peerlink1int = "49"
$template = 'interface Ethernet $peerlink1int
   switchport trunk allowed vlan $Stamps[0]-$Stamps[-1],1111,1234'
#OR $template = Get-Content Your-File | Out-String

# You have to escape not only $, but also [ and ].
$template -replace '\$peerlink1int', $peerlink1int `
            -replace '\$Stamps\[0\]', $stampArray[0] `
            -replace '\$Stamps\[-1\]', $stampArray[-1] `
            | Out-File C:\temp\replaced.txt

or

# Invoke-Expression can only work if your variable is covered with $() to include indecies 
$peerlink1int = "49"
$stamps = @("208","209","210","211")
$template = 'interface Ethernet $peerlink1int
   switchport trunk allowed vlan $($Stamps[0])-$($Stamps[-1]),1111,1234'
$result = Invoke-Expression "`"$template`""

Output you get from both of the above samples is:

interface Ethernet 49
   switchport trunk allowed vlan 208-211,1111,1234

2. Replace multiple values from Array

There are two ways of going about it. 1 is the Invoke-Expression, which can invoke dangerous expressions in your script, and other is the -replace method.

$stampArray = @("208","209","210","211")
$template = 'interface Vlan$stamps
   description V$stamps
   ip address 192.168.$stamps.2/25
   vrrp $stampnum ip 192.168.$stamps.1
   vrrp $stampnum description Stamp$stamps'

# Using Invoke-Expression
$stampnum = 1
$result = @()
foreach($stamps in $stampArray) {
    $result += Invoke-Expression "`"$template`"" #$stamps and $stampnum will be replaced in the document.
    $stampnum ++
}

# Or you can do with the replace command
$stampnum = 1
$result = @()
foreach($stamps in $stampArray) {
    $result += $template -replace '\$stamps', $stamps -replace '\$stampnum', $stampnum++
}

$result | Out-File C:\temp\replaced.txt

And, Output you get is,

interface Vlan208
   description V208
   ip address 192.168.208.2/25
   vrrp 1 ip 192.168.208.1
   vrrp 1 description Stamp208
interface Vlan209
   description V209
   ip address 192.168.209.2/25
   vrrp 2 ip 192.168.209.1
   vrrp 2 description Stamp209
interface Vlan210
   description V210
   ip address 192.168.210.2/25
   vrrp 3 ip 192.168.210.1
   vrrp 3 description Stamp210
interface Vlan211
   description V211
   ip address 192.168.211.2/25
   vrrp 4 ip 192.168.211.1
   vrrp 4 description Stamp211
Jawad
  • 11,028
  • 3
  • 24
  • 37
1
  • Indeed, Invoke-Expression should be avoided.

  • Inside expandable strings ("..."), only simple variable references can be embedded directly (e.g., $stamps), in order to embed expressions - which includes property access and indexing - you need to enclose the expression as a whole in $(...) (e.g., $($Stamps[0])) - see this answer.

  • What you're ultimately looking for is string templating, which PowerShell provides - somewhat obscurely - via on-demand expansion of verbatim (literal) strings as expandable strings via $ExecutionContext.InvokeCommand.ExpandString().


# Define the string template - note the $(...) around
# $Stamps[0] and $Stamps[1]
$template = @'
interface Ethernet $peerlink1int
   switchport trunk allowed vlan $($Stamps[0])-$($Stamps[-1]),1111,1234
'@

# Define the variable referenced in the template
$stamps = '208', '209', '210', '211' # better than: @("208","209","210","211")

# Expand the template.
$revisedBase = $ExecutionContext.InvokeCommand.ExpandString($template)

$revisedBase now contains the following, which is what you expected:

interface Ethernet 
   switchport trunk allowed vlan 208-211,1111,1234

A simple demonstration that you can use the technique in a loop as well, with then-current variable values used in the on-demand expansion:

$template = '`$foo is now: $foo'

foreach ($foo in 1..3) {
  $ExecutionContext.InvokeCommand.ExpandString($template)
}

The above prints:

$foo is now: 1
$foo is now: 2
$foo is now: 3
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • @n0ttsweet: Got it. A few tips: Use `Get-Content -Raw` to read your file in full, as a single multi-line string - piping to `Out-File` is not only less efficient, it appends an extra newline. The more concise and efficient approach is to use the `foreach` statement _as an expression_ to collect its outputs: `$result = foreach ($stamp in $stampArray) { $ExecutionContext.InvokeCommand.ExpandString($Vlans) }` – mklement0 Jan 24 '20 at 22:21