Trim Values inside (one array inside (twoDimentionalArray)) - powershell

I'm trying to remove the leading 0's in all values of an array within a two dimensional array.
Is there a library that can do this for me? I tried it with a foreach loop, but it only trims the current temp variable.
Here is the code:
[String[]]$arr1 = #('str1','str2','str3')
[String[]]$arr2 = #('str4','str5','str6')
[String[]]$arr3 = #('0str7')
[System.Object[]]$twoDimensionalArray = #()
[System.Object[]]$twoDimensionalArray += ,($arr1)
[System.Object[]]$twoDimensionalArray += ,($arr2)
[System.Object[]]$twoDimensionalArray += ,($arr3)
Write-Host "Removing leading zeros from array in twoDimentionalArray..."
ForEach ($strValue in $twoDimensionalArray[2])
{
$strValue = $strValue.TrimStart('0')
}
Write-Host "Leading zero's are removed."
Write-Host ""
Write-Host '$strValue =' $strValue
Write-Host '$twoDimensionalArray[2] =' $twoDimensionalArray[2]
Write-Host ""
Output (0str7)
Removing leading zeros from array in twoDimentionalArray...
Leading zero's are removed.
$strValue = str7
$twoDimensionalArray[2] = 0str7
Desired Output (str7)
Removing leading zeros from array in twoDimentionalArray...
Leading zero's are removed.
$strValue = str7
$twoDimensionalArray[2] = str7
Any ideas?

Here is the modified code snippet with the desired output. The trick is to modify the object itself:
[String[]]$arr1 = #('str1','str2','str3')
[String[]]$arr2 = #('str4','str5','str6')
[String[]]$arr3 = #('0str7')
[System.Object[]]$twoDimensionalArray = #()
[System.Object[]]$twoDimensionalArray += ,($arr1)
[System.Object[]]$twoDimensionalArray += ,($arr2)
[System.Object[]]$twoDimensionalArray += ,($arr3)
Write-Host "Removing leading zeros from array in twoDimentionalArray..."
[int] $arrayCounter = 0
ForEach ($strValue in $twoDimensionalArray[2])
{
$twoDimensionalArray[2][$arrayCounter] = $strValue.TrimStart('0')
$arrayCounter++
}
Write-Host "Leading zero's are removed."
Write-Host ""
Write-Host '$strValue =' $strValue
Write-Host '$twoDimensionalArray[2] =' $twoDimensionalArray[2]
Write-Host ""
I have tested this and it outputs str7 as you are expecting.

Edit all of the jagged array
This might be considered cheating and I don't know if you actual use case will abide by this but I tried to make it a little easier by using an array operator on this jagged array.
$twoDimensionalArray = $twoDimensionalArray | Foreach-object{
,($_ -replace "^0+")
}
This should save back to the original array $twoDimensionalArray. Using -replace save us running a separate loop to going inside the other array.
What this will do is use regex to replace all leading zeros from the array elements. Since the array would then be unrolled we still need the unary operator to ensure that the data is sent down the pipe as an array.
Depending on what control you have on your input this could be simplified as
$arr1 = #('str1','str2','str3')
$arr2 = #('str4','str5','str6')
$arr3 = #('0str7')
[string[]]$twoDimensionalArray = $arr1, $arr2, $arr3 | ForEach-Object{
,($_ -replace "^0+")
}
Edit only one element of the jagged array
Very simple change to get that running using the above logic.
$elementToChange = 2
$twoDimensionalArray[$elementToChange] = [String[]]#($twoDimensionalArray[$elementToChange] -replace "^0+")

Related

Check if a condition is met by a line within a TXT but "in an advanced way"

I have a TXT file with 1300 megabytes (huge thing). I want to build code that does two things:
Every line contains a unique ID at the beginning. I want to check for all lines with the same unique ID if the conditions is met for that "group" of IDs. (This answers me: For how many lines with the unique ID X have all conditions been met)
If the script is finished I want to remove all lines from the TXT where the condition was met (see 2). So I can rerun the script with another condition set to "narrow down" the whole document.
After few cycles I finally have a set of conditions that applies to all lines in the document.
It seems that my current approach is very slow.( one cycle needs hours). My final result is a set of conditions that apply to all lines of code.
If you find an easier way to do that, feel free to recommend.
Help is welcome :)
Code so far (does not fullfill everything from 1&2)
foreach ($item in $liste)
{
# Check Conditions
if ( ($item -like "*XXX*") -and ($item -like "*YYY*") -and ($item -notlike "*ZZZ*")) {
# Add a line to a document to see which lines match condition
Add-Content "C:\Desktop\it_seems_to_match.txt" "$item"
# Retrieve the unique ID from the line and feed array.
$array += $item.Split("/")[1]
# Remove the line from final document
$liste = $liste -replace $item, ""
}
}
# Pipe the "new cleaned" list somewhere
$liste | Set-Content -Path "C:\NewListToWorkWith.txt"
# Show me the counts
$array | group | % { $h = #{} } { $h[$_.Name] = $_.Count } { $h } | Out-File "C:\Desktop\count.txt"
Demo Lines:
images/STRINGA/2XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg images/STRINGA/3XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg images/STRINGB/4XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg images/STRINGB/5XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg images/STRINGC/5XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
performance considerations:
Add-Content "C:\Desktop\it_seems_to_match.txt" "$item"
try to avoid wrapping cmdlet pipelines
See also: Mastering the (steppable) pipeline
$array += $item.Split("/")[1]
Try to avoid using the increase assignment operator (+=) to create a collection
See also: Why should I avoid using the increase assignment operator (+=) to create a collection
$liste = $liste -replace $item, ""
This is a very expensive operation considering that you are reassigning (copying) a long list ($liste) with each iteration.
Besides it is a bad practice to change an array that you are currently iterating.
$array | group | ...
Group-Object is a rather slow cmdlet, you better collect (or count) the items on-the-fly (where you do $array += $item.Split("/")[1]) using a hashtable, something like:
$Name = $item.Split("/")[1]
if (!$HashTable.Contains($Name)) { $HashTable[$Name] = [Collections.Generic.List[String]]::new() }
$HashTable[$Name].Add($Item)
To minimize memory usage it may be better to read one line at a time and check if it already exists. Below code I used StringReader and you can replace with StreamReader for reading from a file. I'm checking if the entire string exists, but you may want to split the line. Notice I have duplicaes in the input but not in the dictionary. See code below :
$rows= #"
images/STRINGA/2XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGA/3XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGB/4XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGB/5XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGC/5XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGA/2XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGA/3XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGB/4XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGB/5XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGC/5XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
"#
$dict = [System.Collections.Generic.Dictionary[int, System.Collections.Generic.List[string]]]::new();
$reader = [System.IO.StringReader]::new($rows)
while(($row = $reader.ReadLine()) -ne $null)
{
$hash = $row.GetHashCode()
if($dict.ContainsKey($hash))
{
#check if list contains the string
if($dict[$hash].Contains($row))
{
#string is a duplicate
}
else
{
#add string to dictionary value if it is not in list
$list = $dict[$hash].Value
$list.Add($row)
}
}
else
{
#add new hash value to dictionary
$list = [System.Collections.Generic.List[string]]::new();
$list.Add($row)
$dict.Add($hash, $list)
}
}
$dict

Match Unique Value in Column and put all values on that row in to variables

I'm working with a CSV with 5 Columns, One of the Columns has unique Values.
Fruit, Number, Car, item, color
apple, 2, Chevy, ball, blue
apple, 1, Ford, ball, green
orange, 3, Ford, string, "red,green"
orange, 5, Mazda, key, red
Banana, 4, Tesla, desk, yellow
I need to search for 3 and have it return orange ford string "red,green" as their own variable
i.e. $fruit1 becomes orange $car1 becomes ford $item becomes string and $color bcomes red,green
I can do the search and have it tell me it found 3, but it still just puts runs everything through $fruit1 and if I tell it to write $fruit1 to a file it just get a repeating mess
I Need to Get output to a TXT file like so
for #3
FRUIT=orange
Car=Ford
ITEM = string
COLOR ="red,green"
whith each value in a different part of the file/newline
I can't post from the machine the script is on. So values changed to match my example
Function LogWrite
{
Param ([string]$logstring)
Add-content $Logfile -value $logstring
}
LogWrite "Started execution of script.ps1"
$masterlist = Import-Csv ($filepath + "\" + "masterlistfile.csv" )
$FruitName = #()
$NumberName = #()
$Carname = #()
$ItemName = #()
$Colorname = #()
$masterlist |ForEach-Object {
$FruitName += $_.fruit
$NumberName += $_.number
$Carname += $_.car
$Itemname += $_.item
$Colorname += $_.color
}
$number = 3
$FruitIdentified
$CarIdentified
$ItemIdentified
$ColorIdentified
LogWrite "NUmber $number to be searched in masterlistfile "
if ($NumberName -eq $number)
{
LogWrite "Number found in the list..."
$Where = [array]::IndexOf($NumberName, $number)
LogWrite "Fruit Name : $FruitrName[$Where] "
$FruitIdentified = $FruitName[$Where]
$CarIdentified = $CarName[$Where]
$ItemIdentified = $ItemName[$Where]
}
You can use the following to read your CSV and then export the result with your expected output:
$number = 3
Import-Csv path/to/csv.csv | ForEach-Object {
if($_.number -eq $number) {
"for #$number"
foreach($prop in $_.PSObject.Properties.Name -ne 'Number') {
'{0}={2}{1}{2}' -f $prop, $_.$prop, ($null, '"')[$prop -eq 'color']
}
}
} | Set-Content path/to/file.ext
Note that Set-Content will overwrite the export file, if you want to append you would use Add-Content as in your function.
To give some context on what the code does:
Read the CSV and convert it to an object with Import-Csv
Loop over all objects and filter where the value of the Number property is equal to $number.
Output for #$number, in this example would be for #3".
Get all properties of the object using PSObject.Properties.Name and exclude the Number property using -ne 'Number'.
Loop over the Property Names and output '{0}={1}' -f $prop, $_.$prop, here we use the Format Operator -f, {0} would be the Property Name and {1} would be the Property Value. {2} will wrap the value with ".." if the Property Name is color.
The output you would be getting using your CSV for input would be:
for #3
Fruit=orange
Car=Ford
item=string
color="red,green"

PowerShell replace text in string after certain expression

I am trying to replace everything in a string after a certain expression is found: In this case ",=dc".
Im kinda new to PowerShell so would appreciate any help. My code only replaces any ",=dc" but not what is behind that
$array += #()
$array += "vu=doc,vu=noc,dc=zet,dc=zez"
$array += "vu=doc,vu=nud,dc=gut,dc=zrt"
$array += "vu=doc,vu=nud,vu=dit,dc=gut,dc=zrt,dc=opt"
foreach ($value in $array){
$arraytype -replace "(?<=(,=dc)).*"
}
I want to only get back the "vu=doc,vu=noc"-part. Everything after the first ",=dc" should be deleted.
It's easier to use an indexed loop if you want the values in the array updated. Also, You can define an array simply by using a comma between the elements. Avoid concatenating with += as this means every time the entire array needs to be recreated in memory, which is both time and memory consuming.
Try
$array = "vu=doc,vu=noc,dc=zet,dc=zez",
"vu=doc,vu=nud,dc=gut,dc=zrt",
"vu=doc,vu=nud,vu=dit,dc=gut,dc=zrt,dc=opt"
for ($i = 0; $i -lt $array.Count; $i++) {
$array[$i] = ($array[$i] -replace 'dc=[^,]+,?').TrimEnd(",")
}
$array
Result:
vu=doc,vu=noc
vu=doc,vu=nud
vu=doc,vu=nud,vu=dit
Regex details:
dc= Match the character string “dc=” literally (case insensitive)
[^,] Match any character that is NOT a “,”
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
, Match the character “,” literally
? Between zero and one times, as many times as possible, giving back as needed (greedy)
You can use IndexOf method to find the position of the string and use Substring method to slice the string up to that position.
$array = #()
$array += "vu=doc,vu=noc,dc=zet,dc=zez"
$array += "vu=doc,vu=nud,dc=gut,dc=zrt"
$array += "vu=doc,vu=nud,vu=dit,dc=gut,dc=zrt,dc=opt"
foreach ($value in $array){
$index = $value.IndexOf(",dc=")
if($index -eq -1) {
Write-Output $value
} else {
Write-Output $value.Substring(0, $index)
}
}
Note: I think you have a typo in the first line($array = #())

Powershell- How to identify via variable, the matched string index on a array

$array = #('blue','red','purple','pink')
$array2 = #('brown','red','black','yellow')
$array | ForEach-Object {
if ($array2 -contains $_) {
Write-Host "`$array2 contains the `$array1 string [$_]"
}
}
how to get the index of the match string?
While PowerShell's -in / -contains operators allow you to test for containment of a given value in a collection (whether a given value is an element of the collection), there is no direct support for getting an element's index using only PowerShell's own features.
For .NET arrays (such as the ones created in your question[1]) you can use their .IndexOf() instance method, which uses case-SENSITIVE comparison based on the current culture; e.g.:
$array.IndexOf('red') # -> 1; case-SENSITIVE, current-culture comparison
Note that PowerShell itself is generally case-INSENSITIVE, and with -eq (and in other contexts) uses the invariant culture for comparison.
A case-INSENSITIVE solution based on the invariant culture, using the Array type's static [Array]::FindIndex() method:
$array = 'blue', 'ReD', 'yellow'
[Array]::FindIndex($array, [Predicate[string]] { 'red' -eq $args[0] }) # -> 1
Note that by delegating to a PowerShell script block ({ ... }) in which each element ($args[0]) is tested against the target value with -eq, you implicitly get PowerShell's case-insensitive, culture-invariant behavior.
Alternatively, you could use the -ceq operator for case-sensitive (but still culture-invariant) matching.
($args[0].Equals('red', 'CurrentCulture') would give you behavior equivalent to the .IndexOf() solution above).
Generally, this approach enables more sophisticated matching techniques, such as by using the regex-based -match operator, or the wildcard-based -like operator.
The above solutions find the index of the first matching element, if any.
To find the index of the last matching element, if any, use:
.LastIndexOf()
[Array]::FindLastIndex()
Note: While there is an [Array]::FindAll() method for returning all elements that meet a given predicate (criterion), there is no direct method for finding all indices.
[1] Note that you do not need #(), the array-subexpression operator to create an array from individually enumerated elements: enumerating them with ,, the array constructor operator alone is enough:
$array = 'blue','red','purple','pink'
Looks like a homework exercise to me. In any case, as mentioned, things are a lot easier if you format your code properly. It's also easier if you name your variables rather than relying on $_, because it changes as it goes through a nested loop.
There are also other ways to do this - do you want the index number or the contents? I assumed the latter
$array = #('blue','red','purple','pink')
$array2 = #('brown','red','black','yellow')
ForEach ($a in $array) {
if ($array2 -contains $a) {
Write-Host "`$array2 contains the `$array1 string $a"
}
}
$array2 contains the $array1 string red
You can try something with an index counter you can use. If $array2.ToLower() contains that element.ToLower(), then loop through that second array to find out where that element actually is.
Note that this is not going to work for large amount of arrays as the time it will take to go through would get larger and larger. But, for small samples like this one, it works fine.
$array = 'blue','Red','purple','pink', 'browN'
$array2 = 'brown','rEd','black','yellow'
$array | ForEach-Object {
if ($array2.ToLower() -contains $_.ToLower()) {
$index = 0
foreach($arrElement in $array2) {
#$index++ # based on index starting with 1
if ($arrElement -eq $_) {
Write-Host "`$array2 contains the `$array1 string [$_] at index: $index"
}
$index++ # based on index starting with 0
}
}
}
# produces output
$array2 contains the $array1 string [Red] at index: 1
$array2 contains the $array1 string [browN] at index: 0
If there are duplicates in the $array2, you'll get two separate lines that would show each index entry.
$array = 'blue','Red','purple','pink', 'browN'
$array2 = 'brown','rEd','black','yellow', 'red'
#Output would be with above code:
$array2 contains the $array1 string [Red] at index: 1
$array2 contains the $array1 string [Red] at index: 4
$array2 contains the $array1 string [browN] at index: 0
You could also do a for loop using an index counter:
$array = 'blue','red','purple','pink', 'black'
$array2 = 'brown','red','black','yellow', 'red'
for ($i = 0; $i -lt $array2.Count; $i++) {
if ($array -contains $array2[$i]) {
Write-Host "`$array2 contains the the string '$($array2[$i])' at index: $i"
}
}
Result:
$array2 contains the the string 'red' at index: 1
$array2 contains the the string 'black' at index: 2
$array2 contains the the string 'red' at index: 4
This is a practical example that uses BinarySearch and relies on your look-up array being sorted by the property of "interest".
Uses IComparer to force case insensitivity
# BinarySearch needs a sorted array
$mySortedArray = Get-ChildItem $env:TEMP | Sort-Object -Property Name
# Provide files available on your machine
$anotherArray = #(
'mat-debug-23484.log'
'MSIaa547.LOG'.ToLower()
)
foreach ($item in $anotherArray) {
$index = $null
# BinarySearch defaults to being case sensitive
$index = [array]::BinarySearch($mySortedArray.Name, $item,[Collections.CaseInsensitiveComparer]::Default)
# If no matches found index will be negative
if ($index -ge 0) {
Write-Host ('Index {0} filename {1} found!' -f $index, $mySortedArray[$index].Name) -ForegroundColor Green
}
}
# Adjusted to meet your example code
$array = #('blue','red','purple','pink')
$array2 = #('brown','red','black','yellow') | Sort-Object
$array | ForEach-Object {
$currentObject = $_
$index = $null
$index = [array]::BinarySearch($array2, $currentObject, [System.Collections.CaseInsensitiveComparer]::Default)
if ($index -ge 0) {
Write-Host ('Index={0} array2 value="{1}" found!' -f $index, $array2[$index]) -ForegroundColor Green
}
}

PowerShell split input and replace/combine?

I want to be able to automatically generate an output if I run a PowerShell script that splits the input by a period "." and adds "DC=" in each item/object that has been split.
$DomainFQDN = "prod.mydomain.com" # This varies depending on the input. It could be "prod.boston.us.mydomain.com" as the input.
$DistinguishedName = $DomainFQDN -split "\."
...
...
...I just don't know how to proceed
How do I get an output of "DC=prod,DC=mydomain,DC=com" for prod.mydomain.com as the input or DC=prod,DC=boston,DC=us,DC=mydomain,DC=com for prod.boston.us.mydomain.com?
Well, you can use foreach construct with $DistinguishedName and use -join like this (if you want to output directly the joined string):
$AddDC = foreach ($e in $DistinguishedName) { "DC=$e" }
Write-Host $($AddDC -join ",")
-join works like -split, you just specify the character that you need to join by.
Other way to do it is to store $AddDC
$AddDC = foreach ($e in $DistinguishedName) { "DC=$e" }
$new_string = $AddDC -join ","
Write-Host $new_string
You can consult this page for more info.
If I got it right, this is what needed:
$fqdn='prod.boston.us.mydomain.com'
$dn="DC=$($fqdn.replace('.',',DC='))"
$dn
$DomainFQDN = "prod.mydomain.com"
$DomainFQDN = $DomainFQDN.Split(".")
For ($i = 0; $i -lt $DomainFQDN.Count; $i++) {
$DomainFQDN[$i] = "DC=" + $DomainFQDN[$i]
}
$DomainFQDN = $DomainFQDN -join ","
Write-Host $DomainFQDN
Output:
DC=prod,DC=mydomain,DC=com
I'm not sure why I couldn't get the -Split "." operator to work. It should function the same as .Split(".") but for some reason it gives a different result. But anyway this should work for you.
I feel like I should mention that a real FQDN would not be DC= on every line. It would look more like:
DC=Com,DC=MyDomain,OU=Prod
I usually do a single replace operation in an expandable to convert from FQDN to distinguished name of the domain root:
$DistinguishedName = "DC=$($DomainFQDN.TrimEnd('.') -replace '\.',',DC=')"
The TrimEnd('.') call strips any dot from rooted FQDNs, and the replace operation replaces each remaining dot with ,DC=