Math from a file.txt - arrays

$space =("`r`n")
$data = #(Get-Content C:\Users\user1\Desktop\ma.txt)
$Array = ($Data.Split($space)).Split($space)
$pos1 = $Array[0][0]+$Array[0][1]
$pos2 = $Array[1][0]+$Array[1][1]
$pos3 = $Array[2][0]+$Array[2][1]
#$pos1
#$pos2
#$pos3
$zahl1 = $Array[0][5]+$Array[0][7]+$Array[0][9]
$zahl1
PowerShell 7.2
txt1.txt has the text:
x1 = 2 + 3
x2 = 8 / 4
x3 = 1 - 4
i want the results (from x1,x2,x3) to be saved at txt2.txt with a command in Terminal.
I tried whith Arrays,
but i only get :2+3 instead of 5
Any thoughts?

You could use Invoke-Expression for this, but read the warning first
Get-Content -Path text1.txt | Where-Object {$_ -match '\S'} | ForEach-Object {
$var,$calculation = ($_ -split '=').Trim()
'{0} --> {1}' -f $var, (Invoke-Expression -Command $calculation)
} | Set-Content -Path text2.txt

This is an attempt of a more secure version, that matches only mathematical expressions, so users cannot run arbitrary code through Invoke-Expression:
Get-Content text1.txt |
Select-String '^\s*(\S+)\s*=([\d\.+\-*/%\(\)\s]+)$' |
ForEach-Object {
$var = $_.Matches.Groups[ 1 ].Value
$expression = $_.Matches.Groups[ 2 ].Value
$result = Invoke-Expression $expression
"{0} = {1}" -f $var, $result
} |
Set-Content text2.txt
The Select-String cmdlet uses a regular expression to match only lines that are considered "safe". Within the RegEx there are two groups defined to split the line into variable (1) and calculation (2) sub strings. These are then extracted via $_.Matches.Groups.
RegEx breakdown:
Pattern
Description
^
line start
\s*
zero or more whitespace characters
(
start 1st capturing group
 \S+
one or more non-whitespace characters
)
end 1st capturing group
\s*
zero or more whitespace characters
=
literal "="
(
start 2nd capturing group
 [
start list of allowed characters
  \d\.+\-*/%\(\)\s
digits, dot, math ops, parentheses, whitespace
 ]
end the list of allowed characters
 +
one or more chars (from the list of allowed characters)
)
end 2nd capturing group
$
line end

Related

Sort an array numerically by extracting integers in names

$Folders = Get-ChildItem -LiteralPath $PSScriptRoot | Where-Object {$_.PSIsContainer} | Select-Object -ExpandProperty BaseName
I get the output
Set 1
Set 10
Set 11 - A Memo
Set 2
Set 20
Set 22 - A Memo With Numbers 1234
Set 3
Set 33 - A Memo
...
$Folders = $Folders | Sort-Object {[INT]($_ -Replace 'Set ', '')} will sort the names in the right order but doesn't work if there is anything after the number like ' - A Memo'.
I've tried \b\d+\b on https://regexr.com but don't know how to implement that in this case.
I need a regex that can extract the number after 'Set ' and discard everything else.
RegEx is a whole other language in itself
Some alternatives for extracting the number, complementing g.sulman's excellent answer.
First the most simplest method, assuming "Set" and the number are always separated by space:
$Folders | Sort-Object { [int]($_ -split ' ')[1] }
This uses the -split operator to split the string on space character, which returns an array. Then it converts the 2nd element to int.
Use -match operator:
$Folders | Sort-Object { [int]( $_ -match '\d+' ? $matches[0] : 0 ) }
Note that conditional operator ? requires PS 7. Alternative for older PS versions:
$Folders | Sort-Object { [int]( if( $_ -match '\d+' ){ $matches[0] } else { 0 } ) }
The -match operator finds the first sub string that matches the RegEx \d+ which stands for one or more digits. The found sub string can be accessed through $matches[0].
Use Select-String cmdlet:
$Folders | Sort-Object { [int] ( $_ | Select-String -Pattern \d+ ).Matches[0].Value }
Same principle as the -match method. Just a different way to access the found sub string.
$names = #"
Set 1
Set 10
Set 11 - A Memo
Set 2
Set 20
Set 22 - A Memo With Numbers 1234
Set 3
Set 33 - A Memo
"# -split "`n"
$names | sort #{expression={[int]($_ -replace '^\w+\s|\s.+')}}
You can use an expression with Sort-Object. Above this is done to replace everything you don't care about and convert to int for number sorting (in text sorting 1, 10, 11, 2, 20 ... is expected.)
Regex breakdown
^ - start of the string
\w - word character (matches S)
+ - the previous thing as many times as need (matches Se, Set, Seet, Seeeeeeeet)
\s - space
| - or. so either everything before this, or everything after
\s - space
. - any character
+ - I think this one's covered above
Note: + matches 1 or more. Use * if you need to match 0 or more.
Edit: As per zett42's helpful comment, you could use [int]($_ -split ' ')[1] in the Sort-Object expression. This splits your name into an array, and takes the 2nd element of that array.

Modify CSV file to add a digit character to a column

I have a problem making a batch file.
This text is inside a file with name export.dat and I have no means to change the CSV field item from 5 to 6 digits.
This is a preferred older program.
As for newer program I can add the required.
D,1,2126,4372,T,125P,,255473730,person,n,person,19800320,07833,300.00,07833,300.00,078330
Has to be after modification:
D,1,2126,4372,T,125P,,255473730,person,n,person,19800320,078330,300.00,078330 - A03.1 - Shigellosis d,,etc
So we have a leading capital D followed by 13 commas and batch should ad a zero before the comma 13.
#echo off
setlocal
set "target=.\export.dat"
set "destination=.\updated.dat"
powershell -noprofile -command^
"$content = get-content -literalpath '%target%';"^
"$modifiedContent = #();"^
"$modifiedContent += $content[0];"^
"ForEach ($line in $content[1 .. ($content.count - 1)]) {"^
" if ($line.startswith('D,')) {"^
" $items = $line.split(',');"^
" if ($items.length -gt 14) {$items[14] += '0'; $line = $items -join ','};"^
" };"^
" $modifiedContent += $line"^
"};"^
"$modifiedContent | set-content -literalpath '%destination%'"
Powershell has Import-CSV and Export-CSV which can handle CSV files, though Export-CSV outputs data with double quoted field items. Powershell 7 introduces arguments for quotes for Export-CSV, though is still new at this time. With Powershell less than 7, the suggestions are to read the file with Get-Content, remove double quotes and use Set-Content. Since the example content lacks double quotes, I decided to use Get-Content, split on commas and write with Set-Content.
The following code is a hybrid batch-file with powershell which I used as a basis to construct the previous code.
It has some comments that explains the code which is not suitable for inserting into a single powershell command.
<# :: Begin bat code
#echo off
setlocal
set "target=.\export.dat"
set "destination=.\updated.dat"
powershell -noprofile "invoke-expression(get-content '%~f0' | out-string)"
exit /b 0
#> ## Begin ps1 code
# Filepaths from environment variables.
$target = $env:target
$destination = $env:destination
# Read file content into a variable.
$content = get-content -literalpath $target
# Create an empty array for modified content.
$modifiedContent = #()
# Add 1st line as unmodified header.
$modifiedContent += $content[0]
# Add the rest of the lines.
foreach ($line in $content[1 .. ($content.length - 1)]) {
# Require a leading capital D to modify a line.
if ($line.startswith('D,')) {
# Split by commas.
$items = $line.split(',')
# Modify item 14.
if ($items.length -gt 14) {
$items[14] += '0'
$line = $items -join ','
}
}
# Add line to array.
$modifiedContent += $line
}
# Write modified content to destination file.
$modifiedContent | set-content -literalpath $destination
If suitable, the powershell code portion could be copied to a .ps1 file and just replace $target = $env:target and $destination = $env:destination with $target = '.\export.dat' and $destination = '.\updated.dat'.
Input export.dat:
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
D,1,2126,4372,T,125P,,255473730,person,n,person,19800320,07833,300.00,07833,300.00,078330
D,1,2126,4372,T,125P,,255473730,person,n,person,19800320,07833,300.00
d,1,2126,4372,T,125P,,255473730,person,n,person,19800320,07833,300.00,07833,300.00,078330
Output updated.dat:
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
D,1,2126,4372,T,125P,,255473730,person,n,person,19800320,07833,300.00,078330,300.00,078330
D,1,2126,4372,T,125P,,255473730,person,n,person,19800320,07833,300.00
d,1,2126,4372,T,125P,,255473730,person,n,person,19800320,07833,300.00,07833,300.00,078330
1st line is the header unmodified.
2nd line is modified.
3rd line is too short in length to modify.
4th line does not start with D, as d, does not match same case so is not modified.

Different output string if I use the cmd line vs a function

I'm trying to transform a date format from "PT10H24M30S" to "10:24:30".
I created the following function:
function PTTime([string] $time){
$pattern = "(\d{2})+"
$matches = $time | Select-String -Pattern $pattern -AllMatches
$newFormat += $matches.Matches | ForEach {$_.Value}
$newFormat = $newFormat -Replace " ", ":"
return $newFormat
}
However, if I call the function I got the output
10 24 30
or
10
24
30
But in the other hand, If I execute the function commands 1 at a time on the command line I got the correct output when I print $newFormat "10:24:30"
I realized that $newFormat was a different type than $returnValue
PS was a bit miss leading here, because when I called $newFormat | Get-Member or $returnValue | Get-Memeber
the output type always is TypeName: System.String.
However, if a do $newFormat.GetType() I got System.String while if I call $returnValue.GetType() I got System.Array[].
I updated the function and now is working as expected
function PTTime([string] $time){
$pattern = "(\d{2})+"
$matches = $time | Select-String -Pattern $pattern -AllMatches
$newFormat = $matches.Matches.Value
return $newFormat -Join ":"
}

Powershell Get-Content + Array

how's it going?
I'm new on Powershell and I'm trying to simplify my code in order that I need to perform the same action in two files, the only thing that changes is the File Name and ReadCount size (15000 for the first file and 50000 for the second one).
When I run it the error shows:
Get-Content : An object at the specified path
C:\Folder\08_configuration_items 11_CI-Contract-new[0].csv does not
exist, or has been filtered by the -Include or -Exclude parameter. At
line:2 char:7
+ $i=0; Get-Content "C:\Folder\$fileArray[$len].csv" -ReadCount $sizeA ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Get-Content : An object at the specified path
C:\Folder\08_configuration_items 11_CI-Contract-new[0]_1.csv does not
exist, or has been filtered by the -Include or -Exclude parameter. At
line:3 char:20
+ ... bookContent = Get-Content "C:\Folder\$fileArray[$len]_1.csv" | Selec ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
That's the code, not sure if I'm accessing the Array using the right way on Powershell.
$sizeArray = #(15000,50000)
$fileArray = #("08_configuration_items", "11_CI-Contract-new")
for($len=0; $len -le 1; $len++) {
$i=0; Get-Content "C:\Folder\$fileArray[$len].csv" -ReadCount $sizeArray[$len] | %{$i++; $_ | Out-File "C:\Folder\$fileArray[$len]_$i.csv" -Encoding "UTF8"}
$WorkbookContent = Get-Content "C:\Folder\$fileArray[$len]_1.csv" | Select -Index 0
for($j=2; $j -le $i; $j++) {
$CurrentFileContent = Get-Content "C:\Folder\$fileArray[$len]_$j.csv"
#($WorkbookContent, $CurrentFileContent) | Set-Content "C:\Folder\$fileArray[$len]_$j.csv"
}
}
Any ideias?
Thanks a lot
The problem here is with string interpolation. A variable name within a string will expand up until it reaches a special character in that name. Then it will append the remainder of the string and any interpolated strings afterwards. This commonly happens with the . character when accessing a property of an object within a string. A simple solution is to use the subexpression operator ($()).
Get-Content "C:\Folder\$($fileArray[$len]).csv"
An alternative is to build the path string another way and then pass it into the command. The method below uses the format operator (-f).
$Path = "C:\Folder\{0}.csv" -f $fileArray[$len]
Get-Content $Path
Your code with the subexpression operator added will look like the following:
$sizeArray = #(15000,50000)
$fileArray = #("08_configuration_items", "11_CI-Contract-new")
for($len=0; $len -le 1; $len++) {
$i=0; Get-Content "C:\Folder\$($fileArray[$len]).csv" -ReadCount $sizeArray[$len] | %{$i++; $_ | Out-File "C:\Folder\$($fileArray[$len])_$i.csv" -Encoding "UTF8"}
$WorkbookContent = Get-Content "C:\Folder\$($fileArray[$len])_1.csv" | Select -Index 0
for($j=2; $j -le $i; $j++) {
$CurrentFileContent = Get-Content "C:\Folder\$($fileArray[$len])_$j.csv"
#($WorkbookContent, $CurrentFileContent) | Set-Content "C:\Folder\$($fileArray[$len])_$j.csv"
}
}
You can see this behavior on a simpler scale using your $fileArray variable.
$filearray
08_configuration_items
11_CI-Contract-new
# Notice how the [0] gets appended to the string-cast $fileArray
"$filearray[0]"
08_configuration_items 11_CI-Contract-new[0]
$filearray[0]
08_configuration_items
"$($filearray[0])"
08_configuration_items
Since $fileArray is an array of strings, you have another unintended effect. With "$fileArray[0]", $fileArray will be interpolated and converted to a string output rather than an array. PowerShell by default will join array elements by a single space when casting as a string. So the resulting output format is arrayItem1 arrayItem2 arrayItem3[0]. [0] is not included as part of the variable evaluation.

Use txt file as list in PowerShell array/variable

I've got a script that searches for a string ("End program" in this case). It then goes through each file within the folder and outputs any files not containing the string.
It works perfectly when the phrase is hard coded, but I want to make it more dynamic by creating a text file to hold the string. In the future, I want to be able to add to the list of string in the text file. I can't find this online anywhere, so any help is appreciated.
Current code:
$Folder = "\\test path"
$Files = Get-ChildItem $Folder -Filter "*.log" |
? {$_.LastWriteTime -gt (Get-Date).AddDays(-31)}
# String to search for within the file
$SearchTerm = "*End program*"
foreach ($File in $Files) {
$Text = Get-Content "$Folder\$File" | select -Last 1
if ($Text | WHERE {$Text -inotlike $SearchTerm}) {
$Arr += $File
}
}
if ($Arr.Count -eq 0) {
break
}
This is a simplified version of the code displaying only the problematic area. I'd like to put "End program" and another string "End" in a text file.
The following is what the contents of the file look like:
*End program*,*Start*
If you want to check whether a file contains (or doesn't contain) a number of given terms you're better off using a regular expression. Read the terms from a file, escape them, and join them to an alternation:
$terms = Get-Content 'C:\path\to\terms.txt' |
ForEach-Object { [regex]::Escape($_) }
$pattern = $terms -join '|'
Each term in the file should be in a separate line with no leading or trailing wildcard characters. Like this:
End program
Start
With that you can check if the files in a folder don't contain any of the terms like this:
Get-ChildItem $folder | Where-Object {
-not $_.PSIsContainer -and
(Get-Content $_.FullName | Select-Object -Last 1) -notmatch $pattern
}
If you want to check the entire files instead of just their last line change
Get-Content $_.FullName | Select-Object -Last 1
to
Get-Content $_.FullName | Out-String

Resources