How to group Get-ChildItem -Recurse results in one output table? - powershell

I'm using the following Powershell command:
Get-ChildItem -Recurse *.txt
But if there's multiple results the output will be like this:
Directory: C:\TestFolder\myfolder\
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- d/m/yyyy hh:MM PM 1234 dragons.txt
Directory: C:\TestFolder\anotherfolder\
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- d/m/yyyy hh:MM PM 66550 jabberwocky.txt
But I want to get grouped results in some form.
Maybe like this:
Mode LastWriteTime Length Directory Name
---- ------------- ------ --------- ----
-a--- d/m/yyyy hh:MM PM 1234 C:\TestFolder\myfolder\ dragons.txt
-a--- d/m/yyyy hh:MM PM 66550 C:\TestFolder\anotherfolder\ jabberwocky.txt
Or this:
Length FullPath
------ --------
1234 C:\TestFolder\myfolder\dragons.txt
66550 C:\TestFolder\anotherfolder\jabberwocky.txt
You probably get the idea. How can I accomplish this, preferably in a simple and elegant manner?
I tried Get-ChildItem -Recurse *.txt | Format-Table but that doesn't do much. I've also checked the most relevant similar questions suggested by Stack Overflow (i.e. "Recurse with PowerShell's Get-ChildItem", and others), but haven't been able to distill a solution so far.
Addendum:
I used help group and found that group is actually the exact alias for the Cmdlet I thought I was looking for: Group-Object. If I do this:
Get-ChildItem -Recurse *.txt | Group-Object "FullName"
I get:
Count Name Group
----- -------- -----
1 C:\TestFold... {C:\TestFolder\myfolder\dragons.txt}
1 C:\TestFold... {C:\TestFolder\anotherfolder\jabberwocky.txt}
But this requires me to simplify with an additional step to:
Get-ChildItem -Recurse *.txt | Group-Object "FullName" | Select-Object "Name"
Which gets:
Name
----
C:\TestFolder\myfolder\dragons.txt
C:\TestFolder\anotherfolder\jabberwocky.txt
If I really want extra properties, then I guess I want to "group on multiple properties", making the question effectively a duplicate of this other SO question.
However, all this "grouping" seems like overkill. Is there not a direct way of using Get-ChildItem to get the output I want?

PowerShell has its own way of displaying System.IO.DirectoryInfo and System.IO.FileInfo objects. If you don't want to see that then you just need to use Select-Object.
Get-ChildItem -Recurse c:\temp | select Mode,LastWriteTime,Length,Directory,Name
Group-Object is completely unnecessary. Given your need I suppose Group-Object seemed appealing but its power is not needed here like it is used for in the linked question. What you really wanted to do is change how PowerShell deals with those objects. Format-Table does not work for the same reason. It was taking the PowerShell by design output and making a table. If you called the properties with Format-Table you would have the same solution as we did with Select-Object.
Get-ChildItem -Recurse c:\temp | Format-Table Mode,LastWriteTime,Length,Directory,Name
Please... Please... don't use that line if you intend to use the output in other functions. Format-cmdlets break objects and are used for the purpose of displaying data only.

If you are just trying to get a list of files recursively with their fullpath names, don't use Group or Select. All of these Commands pretends to be a spreadsheet of objects displayed in a text console.
Instead use the foreach-object operator "%{ }" to dump the raw string date to the console. Example:
Get-ChildItem -Recurse *.txt | %{ $_.fullname }
(Incidentally the above is equivalent to linux command: "find .")
If you want to see which fields are accessible from the foreach-object script block. you can issue this command:
Get-ChildItem my_filepath | get-member
Alternatively, you could pipe the output of Get-ChildItem to Export-Csv command and open it in notepad.
Get-ChildItem -Recurse -file *.txt |
Select FullName |
Export-Csv "files.csv"
notepad files.csv
Alternatively, use cmd:
cmd /c dir /b /s *.txt

Related

How to get a list of files into a Powershell array

I want to get a list of files into a Powershell variable that I can use in subsequent code.
Files:
file1.csv
file2.csv
$sourceFiles = Get-ChildItem .\file*.csv
But the output that I get is:
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 4/10/2022 1:52 AM 31 file1.csv
-a---- 4/10/2022 1:52 AM 31 file2.csv
I am trying to get the output $sourceFiles = "file1.csv","file2.csv"
How is the best way to do this?
You should add -Name to the Get-ChildItem command.
$sourceFiles = Get-ChildItem .\file*.csv -Name
Write-Host $sourceFiles
According with documentation -Name does the job.
Gets only the names of the items in the location. The output is a string object that can be sent down the pipeline to other commands. Wildcards are permitted.
I'm in need of this these days, and I ended up writing a PowerShell script:
$InputFolder = $args[0]
$OutputFile = $args[1]
$BinStream = [System.IO.FileStream]::new($OutputFile,[System.IO.FileMode]::OpenOrCreate)
$BinWriter = [System.IO.BinaryWriter]::new($BinStream)
$InputFolder
| Get-ChildItem
| Sort-Object -Property LastWriteTime
| ForEach-Object {
$Data = $_ | Get-Content -AsByteStream -Raw
$BinWriter.Write($Data)
}
$BinWriter.Close()
You can sort by a different object property, maybe datetime of creation, LastWriteTime just happens to be the one that I needed. The reason I cannot sort by name is that my file names do not have the index suffix padded with zeros.
There's probably a more straight-forward way, without diving into .NET classes, but I don't have much of a PowerShell skill, so whatever I put together to satisfy my immediate need is a result of quick googling and browsing through PowerShell reference online.

Powershell Get-Item not working on some directories like AppData while Get-ChildItem does

Why does Get-Item not work on some directories? For example gi $env:USERPROFILE\AppData returns "Could not find item", but ls $env:USERPROFILE\AppData works fine and can list files?
I want to use gi to pass a string to it to turn it into an object that has other members like LastWriteTime. If I use ls for Get-ChildItem I get the children, i.e. files in the directory, but not the directory.
I can work around this by using a filter on the parent like this: ls -h $env:USERPROFILE | ? {$_.Name -match "AppData"} | select Name,LastWriteTime - but there has to be a better way and it does not explain why gi does not work directly.
The AppData directory has the hidden attribute set:
PS C:\> attrib $env:USERPROFILE\AppData
H C:\Users\username\AppData
The hidden attribute means that Get-Item ignores it by default. The workaround is to use -Force:
PS C:\> Get-Item $env:USERPROFILE\AppData -Force
Directory: C:\Users\username
Mode LastWriteTime Length Name
---- ------------- ------ ----
d--h-- 11/25/2019 12:41 PM AppData

Select-Object with Out-GridView

I am creating a tool for our help desk to copy frequent resolution comments they may use when resolving tickets. I currently have:
Get-ChildItem ".\FileStore" | Out-GridView -PassThru -Title "Quick Notes" | Get-Content | Set-Clipboard
Which outputs something similar to (but in GridView):
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 15/11/2018 14:38 14 1.txt
-a---- 15/11/2018 14:39 14 2.txt
-a---- 15/11/2018 14:39 14 3.txt
-a---- 15/11/2018 14:39 14 4.txt
I am aiming to just have the Name column output, however I am unsure on how to achieve this. I have tried Select, Select-Object and Format-Table which do not work, as I receive the following:
Get-Content : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of
the parameters that take pipeline input.
Is it possible to output only the Name column to the GridView?
To allow Get-Content to find the file, you need to select more than just a Name, because Get-Content have no way to interpret the Name property. It have no matching parameter. Best thing to select is PSPath property, which contains fully qualified PowerShell path? and will match LiteralPath parameter of Get-Content cmdlet.
Sadly Out-GridView does not have direct way to specify which properties to display, but it use standard PowerShell mechanism for selecting them. So, we can use it instead. To do that you need to attach MemberSet property PSStandardMembers with property set DefaultDisplayPropertySet, which says which properties to display by default.
Get-ChildItem ".\FileStore" |
Select-Object Name, PSPath |
Add-Member -MemberType MemberSet `
-Name PSStandardMembers `
-Value ([System.Management.Automation.PSPropertySet]::new(
'DefaultDisplayPropertySet',
[string[]]('Name')
)) `
-PassThru |
Out-GridView -PassThru -Title "Quick Notes" |
Get-Content | Set-Clipboard
That looks very like my answer to a deleted question from user Adam partly surfacing in a follow-up question
My answer (with a different path) was this:
Get-ChildItem -Path ".\FileStore" |
Select-Object Name,FullName |
Out-GridView -PassThru -Title "Quick Notes"|
ForEach-Object{Get-Content $_.Fullname | Set-Clipboard -Append}

Why does grouping the output of a Powershell pipe sometimes not work?

I have read that when the output of Format-Table (with -GroupBy) is not
grouped, you need to 1. sort and 2. pass -AutoSize. Sometimes this does not
seem to work. This is te test case I came up with:
PS C:\> ls -Path C:\Windows\help -Include "*.chm" -Recurse|sort Directory|ft -GroupBy Directory -Auto
Directory: C:\Windows\help
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 23.05.2011 10:54 21544 NVWCPFI.chm
Directory: C:\Windows\help
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 23.05.2011 10:54 21879 NVWCPESM.chm
...
Why is the output not grouped by directory? Why are there two separate entries
for the same directory? How can I group the output? (Please ignore the fact,
that output of the first command is already grouped, I just use this command as
a test case.)
Edit
As pointed out I initially forgot the the Directory argument to sort. (The output still has the same problem.)
I am using Powershell 2.0
In order to group output in Format-Table it first needs to be sorted. A technet blog, which has a discussion on this, is here. While you do have a Sort-Object cmdlet you dont choose a value to sort on. Note I do not get the same output at you with your cmdlet on powershell 3.0
Get-ChildItem -Path C:\Windows\help -Include "*.chm" -Recurse| Sort-Object Directory | Format-Table -GroupBy Directory
The output will indeed be sorted but i gather this is not what you are looking for. Another approach that might be more desired would be to use Select-Object to only output the desired information.
Get-ChildItem -Path C:\Windows\help -Include "*.chm" -Recurse| Sort-Object Directory | Select Name,Directory | Format-Table -AutoSize
You would need to experiment with the values of Select-Object to get your desired output. The next example would give you count information of chm's in folders. Not Format-Table related at all but interesting none the less.
Get-ChildItem -Path C:\Windows\help -Include "*.chm" -Recurse | Group-Object Directory
Try this if work:
...|sort directory |ft -GroupBy...

Wildcard matching in Get-ChildItem in PowerShell

Is there a way to determine how wildcard matching is done in Get-ChildItem?
Various articles (1, 2) suggest that it is done through the WildcardPattern class, but I don’t think this is the case. For example, suppose you have a file in C:\test\test2\testfile.txt. Then Get-ChildItem –Path “C:\*\testfile.txt” will not find the file while WildcardPattern::IsMatch will. Wildcard "*" matching in Get-ChildItem seems to be on directory level: so "\*\" will never match more than one level, like "\A\B\".
So if WildcardPattern class isn't used, then what is?
From what I know, it's using the WildcardPattern as you describe. However, the cmdlet Get-ChildItem limits it to the current directory (characters except \), so it won't conflict with the -Recurse switch that goes to unlimited levels.
With "C:\*\testfile.txt", the asterisk plays a role just for the first level directory (e.g test). The file you're looking for is not there and the output you get is expected. Add another asterisk for the second level and you'll get the desired output (e.g "C:\*\*\testfile.txt"). You can also add the Recurse switch to start searching from the current location, all the way downwards.
Either would work:
gci c:\test\*\testfile.txt
or
gci c:\*\testfile.txt -recurse
Example:
PS C:\temp\test2> dir
Directory: C:\temp\test2
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 4/4/2013 10:41 PM 0 testfile.txt
PS C:\temp\test2> cd \
PS C:\> gci c:\*\testfile.txt -recurse -ea SilentlyContinue
Directory: C:\Temp\test2
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 4/4/2013 10:41 PM 0 testfile.txt