$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.
Related
$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
I'm trying to find the row with an attribute that is larger than the other row's attributes. Example:
$Array
Name Value
---- ----
test1 105
test2 101
test3 512 <--- Selects this row as it is the largest value
Here is my attempt to '1 line' this but It doesn't work.
$Array | % { If($_.value -gt $Array[0..($Array.Count)].value){write-host "$_.name is the largest row"}}
Currently it outputs nothing.
Desired Output:
"test1 is the largest row"
I'm having trouble visualizing how to do this efficiently with out some serious spaghetti code.
You could take advantage of Sort-Object to rank them by the property "Value" like this
$array = #(
[PSCustomObject]#{Name='test1';Value=105}
[PSCustomObject]#{Name='test2';Value=101}
[PSCustomObject]#{Name='test3';Value=512}
)
$array | Sort-Object -Property value -Descending | Select-Object -First 1
Output
Name Value
---- -----
test3 512
To incorporate your write host you can just run the one you select through a foreach.
$array | Sort-Object -Property value -Descending |
Select-Object -First 1 | Foreach-Object {Write-host $_.name,"has the highest value"}
test3 has the highest value
Or capture to a variable
$Largest = $array | Sort-Object -Property value -Descending | Select-Object -First 1
Write-host $Largest.name,"has the highest value"
test3 has the highest value
PowerShell has many built in features to make tasks like this easier.
If this is really an array of PSCustomObjects you can do something like:
$Array =
#(
[PSCustomObject]#{ Name = 'test1'; Value = 105 }
[PSCustomObject]#{ Name = 'test2'; Value = 101 }
[PSCustomObject]#{ Name = 'test3'; Value = 512 }
)
$Largest = ($Array | Sort-Object Value)[-1].Name
Write-host $Largest,"has the highest value"
This will sort your array according to the Value property. Then reference the last element using the [-1] syntax, then return the name property of that object.
Or if you're a purist you can assign the variable like:
$Largest = $Array | Sort-Object Value | Select-Object -Last 1 -ExpandProperty Name
If you want the whole object just remove .Name & -ExpandProperty Name respectively.
Update:
As noted PowerShell has some great tools to help with common tasks like sorting & selecting data. However, that doesn't mean there's never a need for looping constructs. So, I wanted to make a couple of points about the OP's own answer.
First, if you do need to reference array elements by index use a traditional For loop, which might look something like:
For( $i = 0; $i -lt $Array.Count; ++$i )
{
If( $array[$i].Value -gt $LargestValue )
{
$LargestName = $array[$i].Name
$LargestValue = $array[$i].Value
}
}
$i is commonly used as an iteration variable, and within the script block is used as the array index.
Second, even the traditional loop is unnecessary in this case. You can stick with the ForEach loop and track the largest value as and when it's encountered. That might look something like:
ForEach( $Row in $array )
{
If( $Row.Value -gt $LargestValue )
{
$LargestName = $Row.Name
$LargestValue = $Row.Value
}
}
Strictly speaking you don't need to assign the variables beforehand, though it may be a good practice to precede either of these with:
$LargestName = ""
$LargestValue = 0
In these examples you'd have to follow with a slightly modified Write-Host command
Write-host $LargestName,"has the highest value"
Note: Borrowed some of the test code from Doug Maurer's Fine Answer. Considering our answers were similar, this was just to make my examples more clear to the question and easier to test.
Figured it out, hopefully this isn't awful:
$Count = 1
$CurrentLargest = 0
Foreach($Row in $Array) {
# Compare This iteration vs the next to find the largest
If($Row.value -gt $Array.Value[$Count]){$CurrentLargest = $Row}
Else {$CurrentLargest = $Array[$Count]}
# Replace the existing largest value with the new one if it is larger than it.
If($CurrentLargest.Value -gt $Largest.Value){ $Largest = $CurrentLargest }
$Count += 1
}
Write-host $Largest.name,"has the highest value"
Edit: its awful, look at the other answers for a better way.
My goal is to take two objects (created by importing CSVs) that have host names and compare one list to the other and show what's missing from each.
Before I can do the comparison I need to manipulate the host names stored within the object(s). The first step is to use regular expressions to remove (-replace) unnecessary text and then set all host names to lowercase (ToLower()).
I'm not very proficient with modifying existing objects and keeping them "intact", so I'm hoping someone could help me with this.
Here's an example of the data stored within the CSV. The header is on line 7 and each line of data is stored like:
...
7 "name","IP","OSType"
8 "WCSMserver.com","10.10.10.10","OSX"
9 "SERVER2.com","11.11.11.11","Windows"
10 "windowsserver # SERVER2.com","11.11.11.13","Windows"
11 "winner.comSERVER2.com","11.11.11.12","Windows"
...
Here's an example of what I'm trying to do so far (just replacing the name property values):
function ReadExcelReport() {
$global:ConvertedQReportTest = $PSScriptRoot + "\" + "AllSources.csv"
$global:QReportObject = Get-Content -Path $global:ConvertedQReportTest |
Select-Object -Skip 7 |
ConvertFrom-Csv
}
ReadExcelReport
$global:QReportObject.name = $global:QReportObject.name | ForEach-Object {
#($global:QReportObject.name)
$_ -replace 'WCSM \- ' `
-replace '.*?# '`
-replace '.*?#'`
-replace '.*?\:\:.*?'`
-replace '\.cooper\.winner\.com'`
-replace '\.winner\.com'
}
By doing $global:QReportObject.name | ForEach-Object you loop the names of the objects and not the objects.
I've simplified your script a bit (for readability):
$csv = #"
"name"
"WCSMserver-remove this-com"
"SERVER2.com","11.11.11.11"
"windowsserver-remove this-"
"winner.comSERVER2.com"
"#
$global:QReportObject = $csv | ConvertFrom-Csv
$global:QReportObject | Out-Default
$global:QReportObject | ForEach-Object {
$_.name = $_.name -replace '-remove this-'
$_.name = $_.name.ToLower()
}
$global:QReportObject | Out-Default
This will output:
name
----
WCSMserver-remove this-com
SERVER2.com
windowsserver-remove this-
winner.comSERVER2.com
name
----
wcsmservercom
server2.com
windowsserver
winner.comserver2.com
I have the following code:
get-content C:\file.txt | Foreach{($_ | Select-String "$" -all).Matches | measure | select count}
I want to find the number of $ in the file on each line, which works successfully, I am given a tabled count of the number of each character per line. However, I want to output each as a value to an array and perform an arithmetic operation on the count. As it stands everything I've tried has given me a multidimensional array in stead of an array of integers.
For example I've tried
$counts = #(get-content C:\ampersand.txt | Foreach{($_ | Select-String "&" -all).Matches | measure | select count} )
but that just spits out a multidimensional array which I can't perform an arithmetic operation on
I think this is just one of the PowerShell gotcha's. $Counts is not an integer array but an object array with a count property.
get-content C:\file.txt | Foreach{($_ | Select-String "$" -all).Matches | measure | select-object -ExpandProperty count}
I have a list of directory names that I want to convert to absolute paths, and strip out any invalid ones. My initial attempt at doing this was the pipeline
$dirs = 'dir1', 'dir2', 'dir3'
$paths = $dirs | % { Resolve-Path -ea 0 $_ } | Select -ExpandProperty Path
However, what I get back has type [Object[]] rather than [String[]]. I tried ensuring that the paths existed (by adding a ? { Test-Path $_ } step to the pipeline, but that didn't help.
What am I doing wrong? How do I get the directories as a list of strings? I need this so that I can concatenate the array to another array of strings, specifically
$newpath = (($env:PATH -split ';'), $paths) -join ';'
object[] is simply the default array in PowerShell, and it doesn't matter in this (and most) situations. You problem is that you're trying to join to arrays using (arr1, arr2). What this acutally does is create an array with two array objects, because , is an array construction operator. Try to join the arrays using +, like this:
$dirs = 'dir1', 'dir2', 'dir3'
$paths = $dirs | % { Resolve-Path -ea 0 $_ } | Select -ExpandProperty Path
$newpath = (($env:PATH -split ';') + $paths) -join ';'
and you could even skip the splitting, and just do
$dirs = 'dir1', 'dir2', 'dir3'
$paths = $dirs | % { Resolve-Path -ea 0 $_ } | Select -ExpandProperty Path
$newpath = "$env:PATH;$($paths -join ';')"
This seems to work:
$newpath = (#($env:PATH) + $paths) -join ';'
You don't really need to explicitly cast it as [string[]]. Powershell will figure that out from the command context and coerce the data to the proper type.