How do I get only directories using Get-ChildItem? - powershell

I'm using PowerShell 2.0 and I want to pipe out all the subdirectories of a certain path. The following command outputs all files and directories, but I can't figure out how to filter out the files.
Get-ChildItem c:\mypath -Recurse
I've tried using $_.Attributes to get the attributes but then I don't know how to construct a literal instance of System.IO.FileAttributes to compare it to. In cmd.exe it would be
dir /b /ad /s

For PowerShell 3.0 and greater:
Get-ChildItem -Directory
You can also use the aliases dir, ls, and gci
For PowerShell versions less than 3.0:
The FileInfo object returned by Get-ChildItem has a "base" property, PSIsContainer. You want to select only those items.
Get-ChildItem -Recurse | ?{ $_.PSIsContainer }
If you want the raw string names of the directories, you can do
Get-ChildItem -Recurse | ?{ $_.PSIsContainer } | Select-Object FullName

In PowerShell 3.0, it is simpler:
Get-ChildItem -Directory #List only directories
Get-ChildItem -File #List only files

Use
Get-ChildItem -dir #lists only directories
Get-ChildItem -file #lists only files
If you prefer aliases, use
ls -dir #lists only directories
ls -file #lists only files
or
dir -dir #lists only directories
dir -file #lists only files
To recurse subdirectories as well, add -r option.
ls -dir -r #lists only directories recursively
ls -file -r #lists only files recursively
Tested on PowerShell 4.0, PowerShell 5.0 (Windows 10), PowerShell Core 6.0 (Windows 10, Mac, and Linux), and PowerShell 7.0 (Windows 10, Mac, and Linux).
Note: On PowerShell Core, symlinks are not followed when you specify the -r switch. To follow symlinks, specify the -FollowSymlink switch with -r.
Note 2: PowerShell is now cross-platform, since version 6.0. The cross-platform version was originally called PowerShell Core, but the the word "Core" has been dropped since PowerShell 7.0+.
Get-ChildItem documentation: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-childitem

A cleaner approach:
Get-ChildItem "<name_of_directory>" | where {$_.Attributes -match'Directory'}
I wonder if PowerShell 3.0 has a switch that only returns directories; it seems like a logical thing to add.

Use:
dir -r | where { $_ -is [System.IO.DirectoryInfo] }

From PowerShell v2 and newer (k represents the folder you are beginning your search at):
Get-ChildItem $Path -attributes D -Recurse
If you just want folder names only, and nothing else, use this:
Get-ChildItem $Path -Name -attributes D -Recurse
If you are looking for a specific folder, you could use the following. In this case, I am looking for a folder called myFolder:
Get-ChildItem $Path -attributes D -Recurse -include "myFolder"

Less text is required with this approach:
ls -r | ? {$_.mode -match "d"}

The accepted answer mentions
Get-ChildItem -Recurse | ?{ $_.PSIsContainer } | Select-Object FullName
to get a "raw string".
But in fact objects of type Selected.System.IO.DirectoryInfo will be returned. For raw strings the following can be used:
Get-ChildItem -Recurse | ?{ $_.PSIsContainer } | % { $_.FullName }
The difference matters if the value is concatenated to a string:
with Select-Object suprisingly foo\#{FullName=bar}
with the ForEach-operator the expected: foo\bar

Use:
dir -Directory -Recurse | Select FullName
This will give you an output of the root structure with the folder name for directories only.

You'll want to use Get-ChildItem to recursively get all folders and files first. And then pipe that output into a Where-Object clause which only take the files.
# one of several ways to identify a file is using GetType() which
# will return "FileInfo" or "DirectoryInfo"
$files = Get-ChildItem E:\ -Recurse | Where-Object {$_.GetType().Name -eq "FileInfo"} ;
foreach ($file in $files) {
echo $file.FullName ;
}

Use:
Get-ChildItem \\myserver\myshare\myshare\ -Directory | Select-Object -Property name | convertto-csv -NoTypeInformation | Out-File c:\temp\mydirectorylist.csv
Which does the following
Get a list of directories in the target location:
Get-ChildItem \\myserver\myshare\myshare\ -Directory
Extract only the name of the directories:
Select-Object -Property name
Convert the output to CSV format:
convertto-csv -NoTypeInformation
Save the result to a file:
Out-File c:\temp\mydirectorylist.csv

A bit more readable and simple approach could be achieved with the script below:
$Directory = "./"
Get-ChildItem $Directory -Recurse | % {
if ($_.Attributes -eq "Directory") {
Write-Host $_.FullName
}
}
Hope this helps!

My solution is based on the TechNet article Fun Things You Can Do With the Get-ChildItem Cmdlet.
Get-ChildItem C:\foo | Where-Object {$_.mode -match "d"}
I used it in my script, and it works well.

This question is well and truly answered but thought I'd add something extra as I've just been looking at this.
Get-ChildItem happens to produce two types of objects whereas most commands produce just one.
FileInfo and DirectoryInfo are returned.
You can see this by viewing the 'members' available to this command like so:
Get-ChildItem | Get-Member
TypeName: System.IO.DirectoryInfo
TypeName: System.IO.FileInfo
You'll see the various methods and properties available to each type. Note that there are differences. For example that the FileInfo object has a length property but the DirectoryInfo object doesn't.
Anyway, technically, we can return just the directories by isolating the DirectoryInfo object
Get-ChildItem | Where-Object {$_.GetType().Name -eq "DirectoryInfo"}
Obviously as the top answer states the most straightforward solution is to simply use Get-ChildItem -Directory but we now know how to work with multple object types in future :)

Use this one:
Get-ChildItem -Path \\server\share\folder\ -Recurse -Force | where {$_.Attributes -like '*Directory*'} | Export-Csv -Path C:\Temp\Export.csv -Encoding "Unicode" -Delimiter ";"

You can try the PsIsContainer Object
Get-ChildItem -path C:\mypath -Recurse | where {$_.PsIsContainer -eq $true}

To answer the original question specifically (using IO.FileAttributes):
Get-ChildItem c:\mypath -Recurse | Where-Object {$_.Attributes -band [IO.FileAttributes]::Directory}
I do prefer Marek's solution though:
Where-Object { $_ -is [System.IO.DirectoryInfo] }

Related

How to loop through all subfolders in a directory, and remove files with a specific filename

I am trying to write a script in Powershell to remove some files automatically with a certain file name.
My idea is to get all the folders in the directory, then loop through the subdirectory, and remove all items with the file name, but it doesn't seem to be working as expected.
Here is my script
$folders = Get-ChildItem -path "C:\Website-Backup" -Recurse | Where-Object {$_.PsIsContainer} |Group-Object {$_.FullName.Split('_')[0] }
$subfolders = Get-ChildItem -path $folders -Recurse | Where-Object {$_.PsIsContainer} | Group-Object {$_.FullName.Split('_')[0] }
ForEach($subfolder in $subfolders)
{
Remove-Item * -Include *100x*
}
Any idea why the script doesn't seem to be doing anything?
I think you can simplify your code if I understand correctly to:
Get-ChildItem "C:\Website-Backup" -Recurse -include "*100x*" -file | remove-item
The Group-Object command is likely what's confusing things here - Remove-Item is expecting a path - you're not referencing the subfolder variable in your loop as well, so this is the same as just running the Remove-Item command as many times as there are items in the array.
You can try this instead;
Get-ChildItem -Path "C:\Website-Backup" -Recurse | Where-Object -FilterScript { $_.name -like 'MyFile.txt' } | Remove-Item
This will pipe the returned child items into Where-Object, filter it to the specified file name, then pass that to Remove-Item as a file path.
You can also skip the Where-Object, but you lose a bit of control this way;
Get-ChildItem -Path 'C:\WebSiteBackup\*MyFile.txt' -Recurse | Remove-Item

File Sorting Based on Similar File and Folder Names

Im still generally new to powershell, and I am trying to create a program that will take files based on their name, and move them into folders that have a similar name but not exactly the same.
For example, Lets say I have 3 files, Apples.txt, Grapes.txt, and Oranges.txt. And I want to move them into corresponding folders, ApplesUSA, GrapesNY, OrangesFL.
I could just hard code it using a loop and a If-Then Statement. i.e If Apples.txt exists move to ApplesUSA. But I want it to be dynamic, so if other files and folders are added later I dont have to update the code. Is there a way to write a statement that would say if FileA and FolderB are similar in name (both contain apples in the name somewhere) then move fileA to FolderB and so on.
Any help appreciated. Thanks!!!!
try Something like this
$PathWithFile="C:\temp\Test"
$PathWithDir="C:\temp\Test"
Get-ChildItem $PathWithFile -file -Filter "*.txt" | %{
$CurrentFile=$_
$Dirfounded=Get-ChildItem $PathWithDir -Directory | where {$_ -match $CurrentFile.BaseName} | select FullName -First 1
if ($Dirfounded -ne $null)
{
move-Item $CurrentFile.FullName -Destination $Dirfounded.FullName -WhatIf
}
}
A oneliner similar to #Esperento's
gci *.txt -af|%{$File=$_.FullName;gci "$($_.BaseName)*" -ad|%{Move $File -Dest $($_.FullName) -whatif}}
The verbose version:
PushD "X:\path\to\base\folder"
Get-ChildItem *.txt -File | ForEach-Object{
$File = $_.FullName
Get-ChildItem "$($_.BaseName)*" -Directory | ForEach-Object {
Move-Item $File -Destination $_.FullName -whatif
}
}
PopD
Both versions require PowerShell V3 for the -File and -Directory parameters (and their aliases -af/-ad) This can be substituted by an additional |Where-Object{ $_.PSIsContainer} respective | Where-Object{!$_.PSIsContainer}

How to retrieve recursively any files with a specific extensions in PowerShell?

For a specific folder, I need to list all files with extension .js even if nested in subfolders at any level.
The result for the output console should be a list of file names with no extension line by line to be easily copy and pasted in another application.
At the moment I am trying this, but in output console I get several meta information and not a simple list.
Get-ChildItem -Path C:\xx\x-Recurse -File | sort length –Descending
Could you please provide me some hints?
If sorting by Length is not a necessity, you can use the -Name parameter to have Get-ChildItem return just the name, then use [System.IO.Path]::GetFileNameWithoutExtension() to remove the path and extension:
Get-ChildItem -Path .\ -Filter *.js -Recurse -File -Name| ForEach-Object {
[System.IO.Path]::GetFileNameWithoutExtension($_)
}
If sorting by length is desired, drop the -Name parameter and output the BaseName property of each FileInfo object. You can pipe the output (in both examples) to clip, to copy it into the clipboard:
Get-ChildItem -Path .\ -Filter *.js -Recurse -File| Sort-Object Length -Descending | ForEach-Object {
$_.BaseName
} | clip
If you want the full path, but without the extension, substitute $_.BaseName with:
$_.FullName.Remove($_.FullName.Length - $_.Extension.Length)
The simple option is to use the .Name property of the FileInfo item in the pipeline and then remove the extension:
Get-ChildItem -Path "C:\code\" -Filter *.js -r | % { $_.Name.Replace( ".js","") }
There are two methods for filtering files: globbing using an Wildcard, or using a Regular Expression (Regex).
Warning: The globbing method has the drawback that it also matches files which should not be matched, like *.jsx.
# globbing with Wildcard filter
# the error action prevents the output of errors
# (ie. directory requires admin rights and is inaccessible)
Get-ChildItem -Recurse -Filter '*.js' -ErrorAction 'SilentlyContinue'
# filter by Regex
Where-Object { $_.Name -Match '.*\.js$' }
You then can sort by name or filesize as needed:
# sort the output
Sort-Object -PropertyName 'Length'
Format it a simple list of path and filename:
# format output
Format-List -Property ('Path','Name')
To remove the file extension, you can use an select to map the result:
Select-Item { $_.Name.Replace( ".js", "") }
Putting it all together, there is also a very short version, which you should not use in scripts, because it's hardly readable:
ls -r | ? { $_.Name -matches '.*\.js' } | sort Length | % { $_.Name.Replace( ".js", "") | fl
If you like brevity, you can remove the ForEach-Object and quotes. -Path defaults to the current directory so you can omit it
(Get-ChildItem -Filter *.js -Recurse).BaseName | Sort length -Descending
The above Answers works fine. However in WIndows there is a alias called ls the same as on linux so another shorter command that works too would be ls -Filter *.exe
Use BaseName for the file name without the file extension.
Get-ChildItem -Path ".\*.js" | Sort-Object Length -Descending | ForEach-Object {
$_.BaseName
}
I always used cygwin for this in the past. My last employer locked down our environments and it wasn't available. I like to review the latest files I've modified often. I created the following environment variable named LatestCode to store the script. I then execute it with: iex $env:latest code.
Here is the script: get-childitem “.” -recurse -include *.ts, *.html , *.sass, *.java, *.js, *.css | where-object {$_.mode -notmatch “d”} | sort lastwritetime -descending | Select-Object -First 25 | format-table lastwritetime, fullname -autosize

Get-ChildItem results looks like relative paths in Powershell

I would like to scan and move folders (and sub folders or even deeper) from one folder to another using Powershell.
Currently I'm using this pipe of commands.
Get-ChildItem -recurse -path sub\WORK -filter "* OK" | Where-Object { $_.PSIsContainer } | foreach { Move-Item -path $_ -destination sub\OK }
Unfortunately it doesn't work because the found results are relative to .\sub\WORK, when trying to move them Move-Item complains that the folders are not in the current folder:
Move-Item : Cannot find path 'C:\TMP\2011-12-12 test 2 OK' because it does not exist.
I expect that $_ would contain: 'C:\TMP\sub\WORK\2011-12-12 test 2 OK' because these are objects in Powershell and no strings like in Linux.
In case you use Get-ChildItem, be very careful. The best way is to pipe the objects to Move-Item and you don't need to think about it more:
Get-ChildItem -recurse -path sub\WORK -filter "* OK" | Where-Object { $_.PSIsContainer } | Move-Item -destination sub\OK
(no need to use Foreach-Object)
The main reason why I'm answering is this one: Get-ChildItem constructs object differently, depending on the parameters. Look at examples:
PS C:\prgs\tools\Console2> gci -include * | % { "$_" } | select -fir 5
C:\prgs\tools\Console2\-verbose
C:\prgs\tools\Console2\1UpdateDataRepositoryServices.ps1
C:\prgs\tools\Console2\22-52-59.10o52l
C:\prgs\tools\Console2\2jvcelis.ps1
C:\prgs\tools\Console2\a
PS C:\prgs\tools\Console2> gci | % { "$_" } | select -fir 5
-verbose
1UpdateDataRepositoryServices.ps1
22-52-59.10o52l
2jvcelis.ps1
a
Then if you use $_ in a cycle and PowerShell needs to convert FileInfo from Get-ChildItem to string, it gives different results. That happened when you used $_ as argument for Move-Item. Quite bad.
I think there is a bug that reports this behaviour.
You are correct that objects are being piped down the pipeline instead of strings. This is good in that it is more flexible. The drawback is that if you don't explicitly tell the system which property of the object to use you are at the mercy of the system designers. See if explicitly telling the system the property that you want will help:
Get-ChildItem -recurse -path sub\WORK -filter "* OK" | Where-Object { $_.PSIsContainer } | foreach { Move-Item -path $_.Fullname -destination sub\OK }
I just learn't that the PSPath is automatically used in Copy-Item, Move-Item etc. when you don't specify the source in a pipeline, so something like:
gci .\sub\Work | move-item -Destination .\sub\OK
(simplified example)
would work and it would use the PSPath of the passed object to determine the source.
Since the Get-ChildItem returns objects like you said, you can use Get-Member to see what the object has to offer ( that is know about its properties and methods)
Get-ChileItem path | Get-Member
You could see that FullName is one of the properties that you could use.
Here is what worked for me.
Get-ChildItem -Path .\ -Recurse -filter "* OK" | %{Join-Path -Path $_.Directory -ChildPath $_.Name } | Move-Item -Destination sub\OK

Delete files defined in an array with Powershell

Is it possible to define an array of filenames (all files in different folders) and then in a loop delete them all, or do something else?
Actually I need to create a few symbolic links using mklink to one file, putting those links in a different folders, replacing the old links if there was any.
Deleting an array of filenames is simple:
Remove-Item foo.txt,c:\temp\bar.txt,baz\baz.txt
Or via a variable:
$files = 'foo.txt','c:\temp\bar.txt','baz\baz.txt'
Remove-Item $files
And then based on all files in different folders:
$folders = 'C:\temp','C:\users\joe\foo'
Get-ChildItem $folders -r | Where {!$_.PSIsContainer} | Remove-Item -WhatIf
Remove the -WhatIf to do the actual removal.
If you want to delete a file with a specific name you could use the -Filter parameter on Get-ChildItem. This would be the best performing approach:
$folders = 'C:\temp','C:\users\joe\foo'
Get-ChildItem $folders -r -filter foo.bak | Remove-Item -WhatIf
If the name requires more sophisticated matching then you can use a regex in a Where scriptblock e.g.:
$folders = 'C:\temp','C:\users\joe\foo'
Get-ChildItem $folders -r | Where {$_.Name -match 'f\d+.bak$'} |
Remove-Item -WhatIf
something like this should work:
can't test right now, sorry
$filenames = #('filename1.txt', 'filename2.txt', 'filename3.txt')
foreach($file in $filenames)
{
#GCI recursive to find all instances of this filename
$filesToDelete = Get-ChildItem -R | where {$_.Name -eq $file}
foreach($f in $filesToDelete)
{
#delete the file that matches, etc. here
# just using Write-Host to echo out the name for now
Write-Host $f.Name
}
}
As with most powershell, you can really compress this, but wanted to extend for explanation.
You could extend this to match your needs. For example if you needed all files that contain the word "delete", you could do gci | where {$_.Name -like "$file"}