How to join array in pipe - powershell

I wish to join the result from a pipe.
I tried using -join
PS> type .\bleh.log | where { $_ -match "foo"} | select -uniq | $_ -join ','
But that give me this error :/
Expressions are only allowed as the first element of a pipeline.

You could try this :
#(type .\bleh.log | where { $_ -match "foo"} | select -uniq) -join ","
You would need a Foreach-Object (alias %) after the last pipe to have the $_ variable available but it wouldn't help since it holds a single cell value (for each loop iteration).

Related

How to add quotes and commas in PowerShell?

I have a CSV file where I only need 1 Column Called "SerialNumber" I need to combine the text lines, remove any blank space, add each line in quotes and separate by comma.
So far I have multiple miles of code that work, but it adds quotes at the end and doesn't add quotes in the beginning.
$SerialList = import-csv .\log.csv | select -ExpandProperty Serialnumber | Out-File -FilePath .\Process.txt
(gc process.txt) | ? {$_.trim() -ne "" } | set-content process.txt
gc .\process.txt | %{$_ -replace '$','","'} | out-file process1.csv
Get-Content .\process1.csv| foreach {
$out = $out + $_
}
$out| Out-File .\file2.txt
Output:
SerialNumber
1234
1234
4567
4567
Expected Output:
"1234","1234","4567","4567"
Try the following (PSv3+):
(Import-Csv .\log.csv).SerialNumber -replace '^.*$', '"$&"' -join "," > .\file2.txt
(Import-Csv .\log.csv).SerialNumber imports the CSV file and .SerialNumber uses member-access enumeration to extract the SerialNumber column values as an array.
-replace '^.*$', '"$&"' encloses each array element in "...".
Regex ^.*$ matches each array element in full.
Replacement expression "$&" replaces the element with what was matched ($&) enclosed in " chars. - for background, see this answer
-join "," joins the resulting array elements with , as the separator.

Remove duplicate values from hashtables

I am new with powershell and i need to remove duplicate values from my hashtable, eg "c" has a duplicate 3 - "3,4,3" and "d" has duplicate 1 - "1,1,6,4".
[hashtable]$alpha = #{
"a" = "1";
"b" = "2,1,5";
"c" = "3,4,3";
"d" = "1,1,6,4";
"e" = "1,7,9,0";
}
How can I get the result?
I have already tried select-object -unique but doesn't work.
In order to use Select-Object -Unique, your values must be in a collection (array), not inside a single string.
Thus, you must first split the string into an array of tokens (-split operator), and afterwards reassemble the distinct tokens into a string (-join operator).
foreach ($key in #($alpha.Keys)) {
$alpha.$key = ($alpha.$key -split ',' | Select-Object -Unique) -join ','
}
Note the #(...) around $alpha.Keys, which effectively clones the keys collection, which is necessary because you'd otherwise get an error due to trying to modify the collection while it is being enumerated, which is not supported.
In PSv3+ you could use $alpha.Keys.Clone() instead, which is more efficient.
You could also use the pipeline with a call to the ForEach-Object (%) cmdlet (rather than an expression with the foreach loop), but for in-memory data structures a foreach loop is generally the better and faster choice.
For the sake of completeness, here's the pipeline solution (tip of the that to WayneA's answer for refining it):
#($alpha.Keys) | ForEach-Object {
$alpha[$_] = ($alpha[$_.] -split ',' | Select-Object -Unique) -join ','
}
Note that #(...) is still needed around $alpha.Keys, as in the foreach statement solution.
As in the foreach solution, this modifies the hashtable in place.
Another option is to use $alpha.GetEnumerator() in order to force PowerShell to send the hashtable's entries one by one through the pipeline, as [System.Collections.DictionaryEntry] instances representing key-value pairs with a .Key and a .Value property - by default, PowerShell outputs hashtables as a whole.
However, that necessitates creating a new hashtable, because you fundamentally cannot modify a collection being enumerated with .GetEnumerator() (as is also implicitly used by a foreach loop).
$newAlpha = #{} # initialize the new hash table
$alpha.GetEnumerator() | ForEach-Object {
$newAlpha[$_.Key] = ($alpha[$_.Key] -split ',' | Select-Object -Unique) -join ','
}
# $newAlpha now contains the updated entries.
As mentioned, Those are strings, split them first and rejoin after:
$NewAlpha = $Alpha.GetEnumerator() |% { $_.value = ($_.value -split "," | select -unique) -join "," ; $_}
Note: this does not preserve the [hashtable] type.
To do that you will need to employ the approach mentioned in the answer provided by mklement0
#($alpha.Keys) |% {$alpha.$_ = ($alpha.$_ -split ',' | Select-Object -Unique) -join ','}

Inserting Delimiter into PowerShell Output

The result of Get-Process -Name explorer | select Handles,WS,CPU,Id,ProcessName | ft -HideTableHeaders returns the following output:
2623 255336448 178.125 10080 explorer
In order to ingest this data into a third party system, I need to pipe delimit the result as such:
2623|255336448|178.125|10080|explorer
What is the best way to achieve this?
How about:
(Get-Process explorer |
Select-Object Handles,Ws,CPU,ID,ProcessName |
ConvertTo-Csv -Delimiter '|' -NoTypeInformation |
Select-Object -Skip 1) -replace '"',''
Only use ft (Format-Table) for easy viewing in the PowerShell console (it's not good for sending data to other applications, because then you would have to undo the formatting - so don't format in the first place).
To offer a more concise (and slightly faster) alternative to Bill Stewart's helpful answer:
Get-Process -Name explorer | ForEach-Object {
$(foreach ($prop in 'Handles', 'WS', 'CPU', 'Id', 'ProcessName') { $_.$prop }) -join '|'
}
foreach ($prop in 'Handles', 'WS', 'CPU', 'Id', 'ProcessName') { $_.$prop } outputs all the properties of interest for each process object ($_, the input object at hand provided by the ForEach-Object cmdlet).
$(...) collects them as an [object[]] array, which ...
... enables that array's use as the LHS of the -join operator in order to join the array's elements with | as the separator to form a single string.
Overall, you get a single string per input object, as desired.

Select-String sometimes results in "System.Object[]"

I'm working on a script that combines parts of two text files. These files are not too large (about 2000 lines each).
I'm seeing strange output from select-string that I don't think should be there.
Here's samples of my two files:
CC.csv - 2026 lines
LS126L47L6/1L2#519,07448,1,B
LS126L47L6/1R1-1#503,07449,1,B
LS126L47L6/1L3#536,07450,1,B
LS126L47L6/2R1#515,07451,1,B
LS126L47L6/10#525,07452,1,B
LS126L47L6/1L4#538,07453,1,B
GI.txt - 1995 lines
07445,B,SH,1
07446,B,SH,1
07448,B,SH,1
07449,B,SH,1
07450,B,SH,1
07451,B,SH,1
07452,B,SH,1
07453,B,SH,1
07454,B,SH,1
And here's a sample of the output file:
output in myfile.csv
LS126L47L6/3R1#516,07446,1,B
LS126L47L6/1L2#519,07448,1,B
LS126L47L6/1R1-1#503,07449,1,B
System.Object[],B
LS126L47L6/2R1#515,07451,1,B
This is the script I'm using:
sc ./myfile.csv "col1,col2,col3,col4"
$mn = gc cc.csv | select -skip 1 | % {$_.tostring().split(",")[1]}
$mn | % {
$a = (gc cc.csv | sls $_ ).tostring() -replace ",[a-z]$", ""
if (gc GI.txt | sls $_ | select -first 1)
{$b = (gc GI.txt | sls $_ | select -first 1).tostring().split(",")[1]}
else {$b = "NULL"
write-host "$_ is not present in GI file"}
$c = $a + ',' + $b
ac ./myfile.csv -value $c
}
The $a variable is where I am sometimes seeing the returned string as System.Object[]
Any ideas why? Also, this script takes quite some time to finish. Any tips for a newb on how to speed it up?
Edit: I should add that I've taken one line from the cc.csv file, saved in a new text file, and run through the script in console up through assigning $a. I can't get it to return "system.object[]".
Edit 2: After follow the advice below and trying a couple of things I've noticed that if I run
$mn | %{(gc cc.csv | sls $_).tostring()}
I get System.Object[].
But if I run
$mn | %{(gc cc.csv | sls $_)} | %{$_.tostring()}
It comes out fine. Go figure.
The problem is caused by a change in multiplicity of matches. If there are multiple matching elements an Object[] array (of MatchInfo elements) is returned; a single matching element results in a single MatchInfo object (not in an array); and when there are no matches, null is returned.
Consider these results, when executed against the "cc.csv" test-data supplied:
# matches many
(gc cc.csv | Select-String "LS" ).GetType().Name # => Object[]
# matches one
(gc cc.csv | Select-String "538").GetType().Name # => MatchInfo
# matches none
(gc cc.csv | Select-String "FAIL") # => null
The result of calling ToString on Object[] is "System.Object[]" while the result is a more useful concatenation of the matched values when invoked directly upon a MatchInfo object.
The immediate problem can be fixed with selected | Select -First 1, which will result in a MatchInfo being returned for the first two cases. Select-String will still search the entire input - extra results are simply discarded.
However, it seems like the look-back into "cc.csv" (with the Select-String) could be eliminated entirely as that is where $_ originally comes from. Here is a minor [untested] adaptation, of what it may look like:
gc cc.csv | Select -Skip 1 | %{
$num = $_.Split(",")[1]
$a = $_ -Replace ",[a-z]$", ""
# This is still O(m*n) and could be improved with a hash/set probe.
$gc_match = Select-String $num -Path gi.csv -SimpleMatch | Select -First 1
if ($gc_match) {
# Use of "Select -First 1" avoids the initial problem; but
# it /may/ be more appropriate for an error to indicate data problems.
# (Likewise, an error in the original may need further investigation.)
$b = $gc_match.ToString().Split(",")[1]
} else {
$b = "NULL"
Write-Host "$_ is not present in GI file"
}
$c = $a + ',' + $b
ac ./myfile.csv -Value $c
}

powershell script delete first character in output

How would I delete the first line in the output from this command? If the output is A123456, I just want it to show me 123456 with the A.
Get-User $_ | Select sAMAccountName
Just get the substring starting at the second letter(index 1).
Get-User $_ | Select #{n="AccountName";e={$_.sAMAccountName.Substring(1)}}
If you just need the value, you could do it like this:
Get-User $_ | % { $_.sAMAccountName.Substring(1) }
Substring(1) returns a substring containing all chars after the first one.
Get-User $_ | Select #{N="myAccountName";E={$_.sAMAccountName).substring(1)}}
You can remove the first character with the -replace operator:
(Get-User $_ | select -Expand sAMAccountName) -replace '^.'
Or
Get-User $_ | select #{n='sAMAccountName';e={$_.sAMAccountName -replace '^.'}}
if you want to keep objects rather than strings.
$str = "#123456";
$str = $str.Substring(1,($str.Length-1));
$str;