I received a config file of a F5 loadbalancer and was asked to parse it with PowerShell so that it creates a .txt file for every iRule it finds. I'm very new to parsing and I can't seem to wrap my head around it.
I managed to extract the name of every rule and create a separate .txt file, but I am unable to wring the content of the rule to it. Since not all rules are identical, I can't seem to use Regex.
Extract from config file:
ltm rule /Common/irule_name1 {
SOME CONTENT
}
ltm rule /Common/irule_name2 {
SOME OTHER CONTENT
}
What I have for now
$infile = "F5\config_F5"
$ruleslist = Get-Content $infile
foreach($cursor in $ruleslist)
{
if($cursor -like "*ltm rule /*") #new object started
{
#reset all variables to be sure
$content=""
#get rulenames
$rulenameString = $cursor.SubString(17)
$rulename = $rulenameString.Substring(0, $rulenameString.Length -2)
$outfile = $rulename + ".irule"
Write-Host $outfile
Write-Host "END Rule"
#$content | Out-File -FilePath "F5/irules/" + $outfile
}
}
How can I make my powershell script read out what's between the brackets of each rule? (In this case "SOME CONTENT" & "SOME OTHER CONTENT")
Generally parsing involves converting a specific input ("string") into an "object" which PowerShell can understand (such as HTML, JSON, XML, etc.) and traverse by "dotting" through each object.
If you are unable to convert it into any known formats (I am unfamiliar with F5 config files...), and need to only find out the content between braces, you can use the below code.
Please note, this code should only be used if you are unable to find any other alternative, because this should only work when the source file used is code-correct which might not give you the expected output otherwise.
# You can Get-Content FileName as well.
$string = #'
ltm rule /Common/irule_name1 {
SOME CONTENT
}
ltm rule /Common/irule_name2 {
SOME OTHER CONTENT
}
'#
function fcn-get-content {
Param (
[ Parameter( Mandatory = $true ) ]
$START,
[ Parameter( Mandatory = $true ) ]
$END,
[ Parameter( Mandatory = $true ) ]
$STRING
)
$found_content = $string[ ( $START + 1 ) .. ( $END - 1 ) ]
$complete_content = $found_content -join ""
return $complete_content
}
for( $i = 0; $i -lt $string.Length; $i++ ) {
# Find opening brace
if( $string[ $i ] -eq '{' ) {
$start = $i
}
# Find ending brace
elseif( $string[ $i ] -eq '}' ) {
$end = $i
fcn-get-content -START $start -END $end -STRING $string
}
}
For getting everything encompassed within braces (even nested braces):
$string | Select-String '[^{\}]+(?=})' -AllMatches | % { $_.Matches } | % { $_.Value }
To parse data with flexible structure, one can use a state machine. That is, read data line by line and save the state in which you are. Is it a start of a rule? Actual rule? End of rule? By knowing the current state, one can perform actions to the data. Like so,
# Sample data
$data = #()
$data += "ltm rule /Common/irule_name1 {"
$data += "SOME CONTENT"
$data += "}"
$data += "ltm rule /Common/irule_withLongName2 {"
$data += "SOME OTHER CONTENT"
$data += "SOME OTHER CONTENT2"
$data += "}"
$data += ""
$data += "ltm rule /Common/irule_name3 {"
$data += "SOME DIFFERENT CONTENT"
$data += "{"
$data += "WELL,"
$data += "THIS ESCALATED QUICKLY"
$data += "}"
$data += "}"
# enum is used for state tracking
enum rulestate {
start
stop
content
}
# hashtable for results
$ht = #{}
# counter for nested rules
$nestedItems = 0
# Loop through data
foreach($l in $data){
# skip empty lines
if([string]::isNullOrEmpty($l)){ continue }
# Pick the right state and keep count of nested constructs
if($l -match "^ltm rule (/.+)\{") {
# Start new rule
$state = [rulestate]::start
} else {
# Process rule contents
if($l -match "^\s*\{") {
# nested construct found
$state = [rulestate]::content
++$nestedItems
} elseif ($l -match "^\s*\}") {
# closing bracket. Is it
# a) closing nested
if($nestedItems -gt 0) {
$state = [rulestate]::content
--$nestedItems
} else {
# b) closing rule
$state = [rulestate]::stop
}
} else {
# ordinary rule data
$state = [rulestate]::content
}
}
# Handle rule contents based on state
switch($state){
start {
$currentRule = $matches[1].trim()
$ruledata = #()
break
}
content {
$ruledata += $l
break
}
stop {
$ht.add($currentRule, $ruledata)
break
}
default { write-host "oops! $state" }
}
write-host "$state => $l"
}
$ht
Output rules
SOME CONTENT
SOME OTHER CONTENT
SOME OTHER CONTENT2
SOME DIFFERENT CONTENT
{
WELL,
THIS ESCALATED QUICKLY
}
Related
Just for fun a friend and I are trying to find a creative way to send coded messages to eachother using steganography.I stumbled upon doing something like whats shown below and I have been struggling trying to write a function to automate the process.
this is a secret message
can be turned into:
("{2}{1}{0}{3}"-f'ecret m','is a s','this ','essage')
splitting the string and using reordering seems to be the way to go.
So the string needs to be split in random splits between 5-10 characters
.
The index of the original positions need to be saved
the splits need to be swapped around
and the new indexes sorted as to reorder the message properly
i've just really been struggling
help is appreciated
Just for fun .... 😉🤡
$InputMessage = 'this is a secret message'
$SplittedString = $InputMessage -split '' | Select-Object -Skip 1 | Select-Object -SkipLast 1
[array]::Reverse($SplittedString)
foreach ($Character in $SplittedString) {
if ($Character -notin $CharacterList) {
[array]$CharacterList += $Character
}
}
foreach ($Character in ($InputMessage -split '' | Select-Object -Skip 1 | Select-Object -SkipLast 1)) {
$Index = [array]::indexof($CharacterList, $Character)
$Output += "{$Index}"
}
$Result = "'$Output' -f $(($CharacterList | ForEach-Object {"'$_'"}) -join ',')"
$Result
And the output of this would be:
'{6}{10}{9}{3}{5}{9}{3}{5}{2}{5}{3}{0}{8}{7}{0}{6}{5}{4}{0}{3}{3}{2}{1}{0}' -f 'e','g','a','s','m',' ','t','r','c','i','h'
And the output of this would be:
this is a secret message
And now if you want to go fancy with it you remove the curly braces and the quotes and the commas and the -f and add only the numbers and characters to the data. ;-)
Not exactly what you're looking for but this might give you something to start with:
class Encode {
[string] $EncodedMessage
[int[]] $Map
[int] $EncodingComplexity = 3
Encode ([string] $Value) {
$this.Shuffle($Value)
}
Encode ([string] $Value, [int] $Complexity) {
$this.EncodingComplexity = $Complexity
$this.Shuffle($Value)
}
[void] Shuffle([string] $Value) {
$set = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!##$%^&*()_-+=[{]};:<>|./?'
$ref = [Collections.Generic.HashSet[int]]::new()
$ran = [random]::new()
$enc = [char[]]::new($Value.Length * $this.EncodingComplexity)
for($i = 0; $i -lt $enc.Length; $i++) {
$enc[$i] = $set[$ran.Next($set.Length)]
}
for($i = 0; $i -lt $Value.Length; $i++) {
do {
$x = $ran.Next($enc.Length)
} until($ref.Add($x))
$enc[$x] = $Value[$i]
}
$this.EncodedMessage = [string]::new($enc)
$this.Map = $ref
}
}
class Decode {
static [string] DecodeMessage ([Encode] $Object) {
return [Decode]::DecodeMessage($Object.EncodedMessage, $Object.Map, $Object.EncodingComplexity)
}
static [string] DecodeMessage ([string] $EncodedMessage, [int[]] $Map) {
return [Decode]::DecodeMessage($EncodedMessage, $Map, 3)
}
static [string] DecodeMessage ([string] $EncodedMessage, [int[]] $Map, [int] $Complexity) {
$decoded = [char[]]::new($EncodedMessage.Length / $Complexity)
for($i = 0; $i -lt $decoded.Length; $i++) {
$decoded[$i] = $EncodedMessage[$Map[$i]]
}
return [string]::new($decoded)
}
}
Encoding a message:
PS /> $message = 'this is a secret message'
PS /> $encoded = [Encode] $message
PS /> $encoded
EncodingComplexity EncodedMessage Map
------------------ -------------- ---
3 B$h^elu2w#CeeHH^qa siQJ)t}es:.a3 ema=eN(GiIcsO;tst1 .fsg}eSUk7ms4 N>rfe# {49, 2, 41, 27…}
For decoding the message you can either use the object of the type Encode or you can give your friend the Encoded Message and the Map to decode it ;)
PS /> [Decode]::DecodeMessage($encoded)
this is a secret message
PS /> [Decode]::DecodeMessage('B$h^elu2w#CeeHH^qa siQJ)t}es:.a3 ema=eN(GiIcsO;tst1 .fsg}eSUk7ms4 N>rfe#', $encoded.Map)
this is a secret message
In order to create a script which allows me to perform a firewall migration, i have the need to understand how to split an output into sections with Powershell.
The firewall (which is a Sonicwall, if it helps) produces an output, which is delimited by sections. For example:
--System Information--
[Data]
--Network Interfaces--
[Data]
--User Objects Table--
[Data]
...
You can see that the output is delimited by these sections, for which i have produced a regex:
$regex1='^--(\w*|\w+ \w+|\w+ \w+ \w+|\w+ \w+ \w+ \w+)--$'
I don't understand however, how can i produce an output which helps me put a specific section title above, and the data directly below. I don't want all of them, just specific outputs from specific sections.
Any help would be much appreciated,
Thanks a lot in advance
A complex multi-line regex might be a bit to much in your case. A very simple approach would be to go through the content line by line:
$content = #"
--System Information--
[Data1]
--Network Interfaces--
[Data2]
[Data3]
--User Objects Table--
[Data4]
"# -split [System.Environment]::NewLine
$dataDict = #{}
foreach ($line in $content)
{
# Each section opens a new entry in the $dataDict hash table.
# Anything else that follows, gets added to this entry.
if($line -match '^--(.+)--$')
{
$section = $Matches[1]
$dataDict[$section] = #()
}
else
{
$dataDict[$section] += $line
}
}
# You can now narrow down the resulting object to the properties,
# that you are interested in.
[pscustomobject]$dataDict |
Select-Object 'System Information', 'Network Interfaces' |
Format-List
I would prefer an approach with a data table:
$configFile = 'C:\sonciwall\sonicwall.txt'
$dt = New-Object system.Data.DataTable
[void]$dt.Columns.Add('Section',[string]::empty.GetType() )
[void]$dt.Columns.Add('Data',[string]::empty.GetType() )
foreach( $line in Get-Content $configFile ) {
$line = $line.Trim()
if( !$line ) {
Continue # skip empty lines
}
elseif( $line -like '--*' ) {
$section = $line
Continue
}
else {
$data = $line
}
$newRow = $dt.NewRow()
$newRow.Section = $section
$newRow.Data = $data
[void]$dt.Rows.Add( $newRow )
}
# Get specific information from a specific section using sql syntax:
$dt.Select("Section = '--System Information--' AND Data = 'foo'")
# Update specific information in all secions:
$rows = $dt.Select("Data = 'foo'")
foreach( $row in $rows ) {
$row.Data = 'foo bar'
[void]$dt.AcceptChanges()
}
I have two arrays with three elements(file name parts) each. I need to join first element of first array and first element of second array and test if is not null and if the combination(file name) exists and like wise i need to do it for other two elements in a same manner.
$file_nameone_array = ( table, chair, comp)
$file_nametwo_array = ( top, leg , cpu)
foreach ($input_file in $file_nameone_array) {
foreach ($input_rev in $file_nametwo_array) {
$path = "D:\$input_file-$input_rev.txt"
If (test-path $path -pathtype leaf) {
write-host "$path exists and not null"}
else{
write-host "$path doesnot exist"
exit 1}
I expect to test for "table-top.txt", "chair-leg.txt" , "comp-cpu.txt"
whereas my code checks for "table-leg.txt" and exits saying table-leg.txt doesnot exist.
This sounds like a coding problem for a homework assignment (i.e. something you should figure out), so I'll just give you hints instead of the answer.
Your elements of array need to be wrapped in quotes.
Use Write-Output $path to see what you're actually checking for.
Use a regular for loop
This is the syntax to write output of the first element in the array: Write-Output "$($file_nameone_array[0])"
Hopefully you can get this answer from this.
You can try this way :
$file_nameone_array = ( "table","chair", "comp") # INITILIZING ARRAY
$file_nametwo_array = ( "top", "leg", "cpu") # INITILIZING ARRAY
if ($file_nameone_array.Count -ne $file_nametwo_array.Count ) # CPMPARING BOTH ARRAY'S SIZE
{
Write-Error "Both array must have same size..." # THROW ERROR
exit # EXIT
}
for($ind = 0 ; $ind -lt $file_nameone_array.Count ; $ind++) # FOR LOOP 0 TO ARRAY'S LENGTH - 1
{
$path = "D:\\" + $file_nameone_array[$ind] + "-" + $file_nametwo_array[$ind] + ".txt" # COMBINING BOTH ELEMENTS
If (test-path $path -pathtype leaf) # CHECKING PATH EXIST OR NOT
{
write-host "$path exists and not null" # PRINT PATH EXIST
} # END OF IF
else # ELSE
{
Write-Error "$path doesnot exist" # THROW ERROR : FILE NOT EXIST
exit 1 # EXIT SCRIPT
} # END OF ELSE
} # END OF FOR LOOP
If you change your example slightly to just display the $path variable ike this:
$file_nameone_array = #( "table", "chair", "comp" )
$file_nametwo_array = #( "top", "leg" , "cpu" )
foreach ($input_file in $file_nameone_array) {
foreach ($input_rev in $file_nametwo_array) {
$path = "D:\$input_file-$input_rev.txt"
write-host $path
}
}
you get this output
D:\table-top.txt
D:\table-leg.txt
D:\table-cpu.txt
D:\chair-top.txt
D:\chair-leg.txt
D:\chair-cpu.txt
D:\comp-top.txt
D:\comp-leg.txt
D:\comp-cpu.txt
so you can see why it's looking for "D:\table-top.txt" as the first file.
what you can do instead is this:
$file_nameone_array = #( "table", "chair", "comp" )
$file_nametwo_array = #( "top", "leg" , "cpu" )
for( $index = 0; $index -lt $file_nameone_array.Length; $index++ ) {
$input_file = $file_nameone_array[$index];
$input_rev = $file_nametwo_array[$index];
$path = "D:\$input_file-$input_rev.txt"
write-host $path
}
and now you get this output
D:\table-top.txt
D:\chair-leg.txt
D:\comp-cpu.txt
You can replace the write-host with your original file checking logic and you should get the behaviour you were after.
Note - This requires your arrays to be exactly the same length, so you might need to put some error handling in before this bit of code in your script.
Powershell Newbie looking for some help.
I've got a powershell script which changes a priority value inside a text file to have a value of 50:
if ($line.contains("Priority") )
{
$LastComma = $line.LastIndexOf(",") +1
$N = $line.Substring(0,$LastComma)
$N = $N + "50"
$lines[$counter] = $N
}
This works fine and does exactly what I want it to, but I now need to modify it so it changes the priority value to 45 if there is also the following line present:
Provider = XYZ
If this Provider value is not XYZ then all Priority values are set to 50 as before. Anyone have any advice on how can I achieve this?
Thanks
This example illustrates using Powershell with several concepts:
Matching each line using regular expressions
Replacing each line using regular expressions
Using a switch statement
Here's a test file:
Set-Content test.txt "Provider=ABC,Priority=3
Provider=DEF,Priority=4
Not a provider
Provider=XYZ,Priority=5"
You can transform the test file line by line using the
switch statement (thanks #TheIncorrigible1)
switch -Regex -File test.txt
{
'Provider=XYZ.+Priority='
{
$_ -replace "Priority=\d+","Priority=45"
continue
}
'Priority='
{
$_ -replace "Priority=\d+","Priority=50"
continue
}
default
{
$_
continue
}
}
Please make the effort to provide a working example next time.
Without any more information than what was provided, here is a draft of a solution:
foreach ($line in $lines) {
$value = "XYZ"
$check = $false
if ($line.contains("Provider = $value")) {
$check = $true
}
if ($line.contains("Priority")) {
if ($check) {
$priority = 45
} else {
$priority = 50
}
$LastComma = $line.LastIndexOf(",") +1
$N = $line.Substring(0,$LastComma)
$N = $N + "$priority"
$lines[$counter] = $N
}
}
Remark: This assume that the Provider tag will be found prior to the Priority tags.
I'm getting this error:
Array assignment failed because index '3' was out of range.
At Z:\CSP\deploys\aplicacional\teste.ps1:71 char:12
+ $UNAME[ <<<< $i]= $line
+ CategoryInfo : InvalidOperation: (3:Int32) [], RuntimeException
+ FullyQualifiedErrorId : IndexOutOfRange
I really can't find why the index end there.
$CSNAME = #(KPScript -c:GetEntryString $PASSHOME\$PASSFILE -pw:$PASS -Field:csname $SEARCH)
$UNAME = #()
$i = 0
Write-Host "Length="$CSNAME.Length
while($i -le $CSNAME.Length)
{
Write-Host "Start "$i
#$CSNAME[$i].GetType()
if ($CSNAME[0].StartsWith("OK:")) {
Write-Host "ACES $ACES does not exist" -Foreground "red"
}
if ($CSNAME[$i].StartsWith("OK:")) {
break
}
Write-Host "CSNAME="$CSNAME[$i]
$UNAME = $UNAME + $i
$UNAME = KPScript -c:GetEntryString $PASSHOME\$PASSFILE -pw:$PASS -Field:UserName -ref-csname:$CSNAME[$i]
foreach ($line in $UNAME) {
if (! ($line.StartsWith("OK:"))) {
Write-Host $i
$UNAME = $UNAME + $i
Write-Host "uname var"$i
$UNAME[$i] = $line
} else {
Write-Host "break"
break
}
}
#$UNAME[$i].GetType()
#if ($UNAME[$i].StartWith("OK:*")){
# break
#}
Write-Host "UNAME="$UNAME[$i]
#$UNAME[$i]
Write-Host "End "$i
$i += 1
Write-Host "switch"
}
Since the second while is based in the first array length and it has values, why is the it getting out of range?
PowerShell arrays are zero-based, so an array of length 3 has index values from 0 through 2. Your code, however, would iterate from 0 to 3, because the loop condition checks if the variable is less or equal the length (-le):
while($i -le $CSNAME.Length)
{
...
}
You need to check if the variable is less than the length (or less or equal the length minus one):
while($i -lt $CSNAME.Length)
{
...
}
Also, you'd normally use a for loop for iterating over an array, so you can handle the index variable in one place:
for ($i=0; $i -lt $CSNAME.Length; $i++) {
...
}
Edit: You initialize $UNAME as an array, but inside the loop you assign $UNAME = KPScript ..., which replaces the array with whatever the script returns (another array, a string, $null, ...). Don't use the same variable for different things in a loop. Assign the script output to a different variable. Also, your way of appending to the array is rather convoluted. Instead of $UNAME = $UNAME + $i; $UNAME[$i] = $line simply do $UNAME += $line.
$res = KPScript -c:GetEntryString $PASSHOME\$PASSFILE -pw:$PASS -Field:UserName -ref-csname:$CSNAME[$i]
foreach ($line in $res) {
if (! ($line.StartsWith("OK:"))) {
$UNAME += $line
} else {
break
}
}