Retain values from some pipes for final output - powershell

How do you do something like
PS> A | B | C | Format-Table PropertyFromA, PropertyFromB, PropertyFromC
So for example
gci -r -i *.txt | Get-Content | where {$_.Contains("SomeText")} | FormatTable -Property {$_.Directory, $.Name}
In this case gci output will have properties of Directory, Name but these will be lost when I pipe through Get-Content. How do I store this and make use later when piped to Format-Table. Can all this be achieved nicely in a single pipe chain command?

A small modification to your command will work:
gci -r -i *.txt | ? { (gc $_.FullName) -Match "SomeText" } | FormatTable Directory,Name

Arco444 has the right answer for this situation. On the off chance you are not showing us the real reason you are asking this question, or if others make their way here, I am going to show two examples that address this question as well.
Get-ChildItem -Recurse -filter *.txt | ForEach-Object{
$_ | Add-Member -MemberType NoteProperty -Name FileData -Value (Get-Content $_.FullName) -PassThru
} | Where-Object{($_.Filedata).Contains("SomeText")} |
Format-Table name,directory
Get-ChildItem -Recurse -filter *.txt |
Select Name,Directory,#{Label="FileData";Expression={Get-Content $_.FullName}} |
Where-Object{($_.Filedata).Contains("SomeText")} |
Format-Table name,directory
These "oneliners" are both examples that add a property to the objects created by Get-ChildItem. The new property FileData is then what you filter on. This logic can be applied in other ways as well.

Related

How to get five files with most lines in the current directory by simplest way?

There is such a shell command in the chapter "transformational programming" of "The Pragmatic Programmer".
Its function is to list the five files with the most lines in the current directory.
$ find . -type f | xargs wc -l | sort -n | tail -6 | head -5
470 ./debug.pml
470 ./test_to_build.pml
487 ./dbc.pml
719 ./domain_languages.pml
727 ./dry.pml
I'm trying to do the same thing with PowerShell,But it seems too wordy
(Get-ChildItem .\ | ForEach-Object {$_ | Select-Object -Property 'Name', #{label = 'Lines'; expression = {($_ | Get-Content).Length}}} |Sort-Object -Property 'Lines')|Select-Object -Last 5
I believe there will be a simpler way, but I can't think of it.
How to get files with most lines in the current directory by simplest way using PowerShell?
Of course, you don't need to use custom aliases and abbreviations to shorten the length. Although it looks more concise, it loses readability.
Get-Content * | Group-Object PSChildName | Select-Object Count, Name |
Sort-Object Count | Select-Object -Last 5
I finally found my own satisfactory answer!
Used 3 pipeline operators, shell used 5!
What's more, what we get is the object, which can be used for more extensible operations.
I feel better than shell of linux.
dir -file | sort {($_ | gc).Length} | select -l 5
Try either File.ReadLines with Linq or File.ReadAllLines with Count property.
File.ReadLines
Get-ChildItem .\ -File |
Select-Object -Property Name, #{n='Lines'; e= {
[System.Linq.Enumerable]::Count([System.IO.File]::ReadLines($_.FullName))
}
} | Sort-Object -Property 'Lines' -Descending | Select-Object -First 5
File.ReadAllLines
Get-ChildItem .\ -File |
Select-Object -Property Name, #{n='Lines'; e= {
[System.IO.File]::ReadAllLines($_.FullName).Count
}
} | Sort-Object -Property 'Lines' -Descending | Select-Object -First 5
A fast approach would be to use switch -File:
$files = (Get-ChildItem -File ).FullName
$result = foreach ($file in $files) {
$lineCount = 0
switch -File $file {
default { $lineCount++ }
}
[PsCustomObject]#{
File = $file
Lines = $lineCount
}
}
$result | Sort-Object Lines | Select-Object -Last 5

Getting original object after piping via Out-GridView

I need to show a list of file names without paths and open the selected file.
I can get it to work with full paths:
Get-ChildItem *.txt -Recurse | Sort-Object Name| Out-GridView -PassThru | Invoke-Item
But when I try to show only the file names it fails:
Get-ChildItem *.txt -Recurse | Sort-Object Name| Select-Object Name | Out-GridView -PassThru | Invoke-Item
By piping it through Get-Member I understand that Select-Object Name striped all non-Name properties. So how can I trace the original file object from what I got from GridView?
You might want to use the DefaultDisplayPropertySet property of the hidden PSStandardMembers set for this:
$defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',[string[]]#('Name'))
$PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]#($defaultDisplayPropertySet)
Get-ChildItem *.txt -Recurse | Select-Object * |
ForEach-Object {
$_ | Add-Member MemberSet PSStandardMembers $PSStandardMembers; $_
} | Out-Gridview -PassThru | Select-Object FullName
Get-ChildItem *.txt has a default display propery set of: LastWriteTime, Length, Name
Select-Object * strips off the complete property set (displays all properties)
Add-Member MemberSet PSStandardMembers $PSStandardMembers adds a new display set with just the Name property and keeps the rest of the properties hidden
Select-Object FullName reveals the hidden FullName property
The problem is, that the Invoke-Item needs the path and not only the filename.
You could store the get-childitem in a temporary variable:
$tmp = Get-ChildItem *.txt -Recurse | Sort-Object Name
$tmp | Select-Object Name | Out-GridView -PassThru
$tmp | Invoke-Item
Is that what you wanted? Please let me know if it worked, and if it did please mark my post as the answer. :)

Copy files listed in a txt document, keeping multiple files of the same name in PowerShell

I have a bunch of lists of documents generated in powershell using this command:
Get-ChildItem -Recurse |
Select-String -Pattern "acrn164524" |
group Path |
select Name > test.txt
In this example it generates a list of files containing the string acrn164524 the output looks like this:
Name
----
C:\data\logo.eps
C:\data\invoice.docx
C:\data\special.docx
InputStream
C:\datanew\special.docx
I have been using
Get-Content "test.txt" | ForEach-Object {
Copy-Item -Path $_ -Destination "c:\destination\" -Recurse -Container -Force
}
However, this is an issue if two or more files have the same name and also throws a bunch of errors for any lines in the file that are not a path.
sorry if I was not clear enough I would like to keep files with the same name by appending something to the end of the file name.
You seem to want the files, not the output of Select-String. So let's keep the files.
Get-ChildItem -Recurse -File | Where-Object {
$_ | Select-String acrn164524 -Quiet
} | Select-Object -ExpandProperty FullName | Out-File test.txt
Here
-File will make Get-ChildItem only return actual files. Think
about using a filter like *.txt to reduce the workload more.
-Quiet will make Select-String return $true or $false, which
is perfect for Where-Object.
Instead of Select-Object -ExpandProperty X in order to retrieve an array of raw property values (as opposed to an array of PSObjects, which is what Select-Object would normally do), it's simpler to use ForEach-Object X instead.
Get-ChildItem -Recurse -File | Where-Object {
$_ | Select-String acrn164524 -Quiet
} | ForEach-Object FullName | Out-File test.txt

Listing Folder Size by Piping

How can I list size of each folder in a directory by the sum of all files in each folder/subfolders?
My latest attempt:
ls | foreach-object { select-object Name, #{Name = "Size"; Expression = { ls $_ -recurse | measure-object -property length -sum } }
I've made other attempts but nothing successful yet. Any suggestions or solutions are very welcome. I feel like I'm missing something very obvious.
The output should look as follows:
Name Size
And it should list each folder in the root folder and the size of the folder counting subfolders of that folder.
I was able to resolve the issue with the following:
param([String]$path)
ls $path | Add-Member -Force -Passthru -Type ScriptProperty -Name Size -Value {
ls $path\$this -recurse | Measure -Sum Length | Select-Object -Expand Sum } |
Select-Object Name, #{Name = "Size(MB)"; Expression = {"{0:N0}" -f ($_.Size / 1Mb)} } | sort "Size(MB)" -descending
I think you've basically got it, honestly.
You could be a bit more elegant by using Add-Member:
ls | Add-Member -Force -Passthru -Type ScriptProperty -Name Length -Value {
ls $this -recurse | Measure -Sum Length | Select -Expand Sum }
PSCX messes with the formatting and will output "" for the size even though you've actually got a size. If you're using PSCX you'll have to add an explicit | Format-Table Mode, LastWriteTime, Length, Name -Auto
It's not particularly elegant but should get the job done:
gci . -force | ?{$_.PSIsContainer} |
%{$res=#{};$res.Name=$_.Name; $res.Size = (gci $_ -r | ?{!$_.PSIsContainer} |
measure Length -sum).Sum; new-object psobject -prop $res}
Note the use of -Force to make sure you're summing up hidden files. Also note the aliases I have used (convenient when typing interactively). There's ? for Where-Object and % for Foreach-Object. Saves the wrists. :-)
Here is a handy Powershell example script that may be adapted to fit what you are looking for.

output filename, not string with select-string

I'm using powershell to "grep" my source code for a particular string. If the string is in the file, I would like the name of the file, not the line of code that contains the string.
I would also like the name of the file, just once, not listed for as many times as the file exists.
I'm currently using:
gci . -include "*.sql" -recurse | select-string -pattern 'someInterestingString'
Now I understand that the output of select-string is some sort of ojbect, and what I'm seeing in the console is, i'm guessing, the ToString() of that object. I assume that I could use format-table to control the output of the select-string, and I suppose sort to get distinct values only.
but that's a lot of guessing.
I don't think I completely understand what you're trying to do. If you want the output grouped by file, you can pipe into Format-Table with the -GroupBy parameter:
gci . -include "*.sql" -recurse `
| select-string -pattern 'someInterestingString' `
| Format-Table -GroupBy Path
If you want to get only the names of the files that match without any other info, you can use Select-Object with the -Unique parameter:
gci . -include "*.sql" -recurse `
| select-string -pattern 'someInterestingString' `
| Select-Object -Unique Path
If you're interested in only the file name, regardless whether the name itself appears multiple times in your hierarchy, then you can select the Filename property instead.
Note: The Get-Member cmdlet is a great help in figuring out what properties exist on an object:
gci . -include "*.sql" -recurse `
| select-string -pattern 'someInterestingString' `
| Get-Member
You can also use its alias gm instead.
When I'm doing this I just use the -List parameter - yes it does display the line of code but you only get one line per file (no matter how many matches there are):
PS> Get-ChildItem . -r *.cs | Select-String XmlNode -list
Commands\SnapinHelp\CmdletInfo.cs:27: public List<XmlNode> InputTypes;
Commands\SnapinHelp\GetSnapinHelpCommand.cs:124: WriteXmlNodeList(c...
Commands\SnapinHelp\ParameterInfo.cs:73: XmlNode FindNode(XmlDocument doc)
Commands\Xml\XmlCommandBase.cs:65: RegisterInputType<XmlNode>(Proce...
If you want the path:
PS> Get-ChildItem . -r *.cs | Select-String XmlNode -list |
Format-Table Path
Path
--------
C:\Users\Keith\Pscx\Src\PscxSnapin\Commands\SnapinHelp\CmdletInfo.cs
C:\Users\Keith\Pscx\Src\PscxSnapin\Commands\SnapinHelp\GetSnapinHelpCommand.cs
C:\Users\Keith\Pscx\Src\PscxSnapin\Commands\SnapinHelp\ParameterInfo.cs
C:\Users\Keith\Pscx\Src\PscxSnapin\Commands\Xml\XmlCommandBase.cs
Or if you really only want the filename:
PS> Get-ChildItem . -r *.cs | Select-String XmlNode -list |
Format-Table Filename
Filename
--------
CmdletInfo.cs
GetSnapinHelpCommand.cs
ParameterInfo.cs
XmlCommandBase.cs
I found it easier to do
(...|select-string "search").Path