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.
Related
I have a situation here and I would like to share it with you to ask for help.
I have a TXT file that I receive every day and I need to import it into my ERP, however, this file comes with a line break that we have to manually adjust
And when adjusted, it looks like this:
{
Write-Error "Informe um arquivo para leitura"
Return
}
$arquivo = $args[0]
if(![System.IO.File]::Exists($arquivo))
{
Write-Error "Arquivo nao encontrado: $arquivo"
Return
}
$tamanhoEsperado = 240
$ultimoTamanho = 0
foreach($linha in [System.IO.File]::ReadLines($arquivo))
{
if ($linha.length -gt 0)
{
if (!($linha.length -eq $tamanhoEsperado) -And (($linha.length + $ultimoTamanho) -eq $tamanhoEsperado))
{
Write-Host -NoNewLine $linha
$ultimoTamanho += $linha.length
}
else
{
if ($ultimoTamanho -gt 0)
{
Write-Host
}
Write-Host -NoNewLine $linha
$ultimoTamanho = $linha.length
}
}
else
{
Write-Host
}
}
But I am not able to make the process automatic with this script.
Powershell will look for the TXT file in a specific folder, validate if the file has 240 positions and if not, correct that line break shown in img1. Would that be possible?
Note:
Write-Host is typically the wrong tool to use, unless the intent is to write to the display only, bypassing the success output stream and with it the ability to send output to other commands, capture it in a variable, or redirect it to a file. To output a value, use it by itself; e.g., $value instead of Write-Host $value (or use Write-Output $value). See this answer for more information.
Therefore, your code only produced for-display output, not data.
Try something like the following:
$fragment = ''
$correctedLines =
foreach ($line in [System.IO.File]::ReadLines($arquivo)) {
if ($fragment) { # The previous line was incomplete.
$fragment + $line # Concatenate the fragment with the current line and output.
$fragment = '' # Reset the fragment variable.
} elseif ($line.Length -ne 240) {
$fragment = $line # Save the fragment and continue.
} else {
$line # Complete line -> output it.
}
}
Note the use of implicit output (e.g., $line) and that you can directly collect all output from the foreach statement in a variable.
I have got a challenge to calculate 1000! with Powershell as fast as possible.
Here the given rules for this code-challenge:
no predefined arrays or strings (except for initial 0!-value)
no use of external modules or embedded C# code
routine must be correct for any input from 0 till 1000
result-string must be created as part of the measurement
Based on this conditions I could create the below code-snippet as a first draft.
Is there any idea to improve the speed? Inputs are more than welcome!
cls
Remove-Variable * -ea 0
$in = 1000
$runtime = measure-command {
# define initial arr with 0! = 1:
$arr = [System.Collections.Generic.List[uint64]]::new()
$arr.Add(1)
if ($in -gt 1) {
# define block-dimension per array-entry:
$posLen = 16
$multiplier = [uint64][math]::Pow(10,$posLen)
# calculate faculty:
$start = 0
foreach($i in 2..$in) {
$div = 0
if ($arr[$start] -eq 0){$start++}
foreach($p in $start..($arr.Count-1)) {
$mul = $i * $arr[$p] + $div
$arr[$p] = $mul % $multiplier
$div = [math]::Floor($mul/$multiplier)
}
if ($div -gt 0) {$arr.Add($div)}
}
}
# convert array into string-result:
$max = $arr.count-1
$faculty = $arr[$max].ToString()
if ($max -gt 1) {
foreach($p in ($max-1)..0) {
$faculty += ($multiplier + $arr[$p]).ToString().Substring(1)
}
}
}
# check:
if ($in -eq 1000 -and !$faculty.StartsWith('402387260077') -or $faculty.length -ne 2568) {
write-host 'result is not OK.' -f y
}
# show result:
write-host 'runtime:' $runtime.TotalSeconds 'sec.'
write-host "`nfaculty of $in :`n$faculty"
The fastest way is to rely on the existing multiplication capabilities of a data type designed specifically for large integers - like [bigint]:
$in = 1000
$runtime = Measure-Command {
# handle 0!
$n = [Math]::Max($in, 1)
$b = [bigint]::new($n)
while(--$n -ge 1){
$b *= $n
}
}
Clear-Host
Write-Host "Runtime: $($runtime.TotalSeconds)"
Write-Host "Factorial of $in is: `n$b"
This gives me a runtime of ~18ms, contrasting with ~300ms using your [uint64]-based carry approach :)
As Jeroen Mostert points out, you may be able to attain an additional improvement by side-stepping the *= operator and calling [BigInt]::Multiply directly:
# change this line
$b *= $n
# to this
$b = [bigint]::Multiply($b, $n)
I believe all the constraints are met as well:
no predefined arrays or strings (except for initial 0!-value)
Check!
no use of external modules or embedded C# code
Check! ([bigint] is part of the .NET base class library)
routine must be correct for any input from 0 till 1000
Check!
result-string must be created as part of the measurement
We're already tracking the result as an integer, thereby implicitly storing the string representation
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
}
I've got next data into variable $out (type is Object[]):
Success...
Go!
0:#217> trace && .quit
0x000 Subline : _OK
0x008 Timed : NO
0x016 Check : _OK
0x022 Post :
0x030 Offset : None
0x038 Hint : False
0x050 NextHint : False
quit:
I need extract text between string 0:#217> trace && .quit and quit:
I wrote:
[Regex]::Match($out, "(?<=.quit').+?(?=quit:)").Value
But this extracts required data into a line (type String), not a column (Object[]). How to fix this?
P.S.
I solved the problem by myself as follows
([Regex]'(?is)(?:(?<=\.quit).+(?=quit:))').Match(($out -join "`n")).Value
But maybe there is more perfect way to do this?
Problem solved
([Regex]'\s+0x([^q]+)').Match(($out -join "`n")).Value
Just use the -split function to create a String[] of your result:
$result = ([regex]::Match($a, '\.quit(.*)quit:').Groups[1].value) -split [System.Environment]::NewLine
[Edit: this will work if $out is a String[], e.g. from $out = Get-Content results.txt, from your other comments, you might have something different].
As a general approach, have a true/false flag which chooses whether lines are allowed through or not, and when you see the first line then set the flag, and when you see the last line you want, change the flag.
$middle = foreach ($line in $out) {
if ($line -match '^quit') { $allow = $false }
if ($allow) { write-output $line }
if ($line -match '0:#217>') { $allow = $true }
}
The ordering of the tests determines whether the start or end lines show up in the results or not.
This can be shortened on the console for typing, into something like:
# loop # end line clears flag # print? # start line sets flag
$out |% { if($_ -match '^quit'){$f=0}; if ($f){$_}; if ($_ -match '0:#217>'){$f=1} }
This might work, but it makes some assumptions:
$out -match '^ '
$out is always a String[]. Force it with #($out) if it might be a single string.
You are using PowerShell v4 or v5, so -operator will act as a filter on an array.
Your example data is accurate, and all the lines you want start with a space, and all the other lines do not.
The following is for very simple searches, get the string data between a starting string and ending string.
Upsides
Very simplistic
Downsides
Does poorly for data with multiple matches etc
Code
Function Get-StringBetweenStartEnd {
Param($Text,$Start,$End)
$Regex = [Regex]::new("(?<="+$Start+")(.*)(?="+$End+")")
$Match = $Regex.Match($String)
if($Match.Success) { Return $Match.Value}else{Return ""}
}
Example Usage
$String = "Test: disconnected: 10.10.10.1::59270 (VNC Viewer closed)"
$Result = Get-StringBetweenStartEnd -Text $String -Start "nected:" -End "::"
$Result.Trim()
Output:
10.10.10.1
Basicly I am trying to find a way to put a variable for replacing a line in powershell.
The current script:
$switches = get-outlookinbox | where subject -eq "Hello"
$e = $switches.body
$e = $e.replace("Hello:","")
$e = $e.replace(" Number","")
$e = $e.replace(":1","")
$e = $e.replace(":2","")
$e = $e.replace(":3","")
$e = $e.replace(":4","")
$e = $e.replace(":99","")
You can see what I am going for here... But I don't want 99 lines of replace code, any thoughts on this?
Also, the numbers MUST have : infront of it, otherwise the replace will corrupt the file since it contains only IP's and ports, it's the ports I want to remove from the output.
You can use a simple foreach loop and iterate from 99 to 1:
foreach ($n in 99..1)
{
$e = $e.Replace(":$n", " ")
}
Or, if you prefer one line:
foreach ($n in 99..1) { $e = $e.Replace(":$n", " ") }
Demo:
PS > $mystr = "a:1:2:3:4:5:6:7:8:9:10:11:12:13:14:15:16:17:18:19:20b"
PS > foreach ($n in 20..1) { $mystr = $mystr.replace(":$n", "") }
PS > $mystr
ab
PS >
Regular expressions : regex101.com
$e = $e -replace ':\d+',""
No loops necessary
Might as well get 'em all while you're at it:
$e -replace 'Hello:| Number|:\d{1,2}'