I want to exclude all directories from my search in PowerShell. Both FileInfo and DirectoryInfo contain Attributtes property that seems to be exactly what I want, but I wasn't able to find out how to filter based on it. Both
ls | ? { $_.Attributes -ne 'Direcory' }
ls | ? { $_.Attributes -notcontains 'Direcory' }
didn't work. How can I do this?
You can use the PSIsContainer property:
gci | ? { !$_.PSIsContainer }
Your approach would work as well, but would have to look like this:
gci | ? { !($_.Attributes -band [IO.FileAttributes]::Directory) }
as the attributes are an enum and a bitmask.
Or, for your other approach:
gci | ? { "$($_.Attributes)" -notmatch "Directory" }
This will cause the attributes to be converted to a string (which may look like "Directory, ReparsePoint"), and on a string you can use the -notmatch operator.
PowerShell v3 finally has a -Directory parameter on Get-ChildItem:
Get-ChildItem -Directory
gci -ad
Exclude directories in PowerShell:
Get-ChildItem | Where-Object {$_ -isnot [IO.DirectoryInfo]}
Or it's terse, but harder to read version:
gci | ? {$_ -isnot [io.directoryinfo]}
Credit goes to #Joey for his insightful comment using the -is operator :)
However
Technically, I prefer including only Files or only Directories since excluding can lead to unexpected results as Get-ChildItem can return more than just files and directories :)
Include just Files:
Get-ChildItem | Where-Object {$_ -is [IO.FileInfo]}
Or:
gci | ? {$_ -is [io.fileinfo]}
Include just Directories:
Get-ChildItem | Where-Object {$_ -is [IO.DirectoryInfo]}
Or:
gci | ? {$_ -is [io.directoryinfo]}
You can also filter out directories by looking at their type directly:
ls | ?{$_.GetType() -ne [System.IO.DirectoryInfo]}
Directories are returned by get-childitem (or ls or dir) of type System.IO.DirectoryInfo, and files are of type System.IO.FileInfo. When using the types as literals in Powershell you need to put them in brackets.
Timings for different approaches, times of 5000 itarations on a short dir / 25 iterations on System32 dir. Measuring is done using:
(measure-command { for ($i=0;$i -lt 5e3;$i++) {
$files= gci | ? { !$_.PSIsContainer } # this is target to measure
} } ).totalmilliseconds
Results from slowest to fastest (all lines have same end result):
$files= gi *.* | ? { !$_.PSIsContainer } #5000 iterations / 25long = 15.2sec / 22s
$files= foreach ($file in gi *.*) { if ($file.mode -notmatch 'd') { $file } } # 11.8s / 20s
$files= gci | ? { !($_.Attributes -band [IO.FileAttributes]::Directory) } # 8.9s / 10.7s
$files= Get-ChildItem | Where-Object {$_.mode -notmatch 'd'} # 8.8s / 10.6s
$files= gci | ? { !$_.PSIsContainer } # 7.8s / 9.8s
$files= Get-ChildItem | ? {$_ -isnot [IO.DirectoryInfo]} # 7.6s / 9.6s
$files= gci | Where-Object {$_ -is [IO.FileInfo]} # 7.6s / 9.6s
$files= foreach ($file in gci *.*) { if ($file.mode -notmatch 'd') { $file } } #7.3s / 12.4s
$files= #( foreach ($file in gci) { if ($file.mode -notmatch 'd') { $file } } ) #3.7s / 6.4s
$files= foreach ($file in gci) { if ($file.mode -notmatch 'd') { $file } } # 3.7s / 6.4s
Notice that specifying "*.*" will almost double process time. That's why GCI without parameters is fastest than GI which must use *.* parameter.
Related
im quiet new to powerhsell and I have the following goal:
My code is supposed to loop through selected subfolders and compare those. The names of the subfolders are identical in both parent folders, however the path before those selected folders are different: C:\temp\parentF1\BackUp* and C:\temp\parentF2\BackUp*
The problem that I have is that even tho I think my $vars that I use for the comparison should have a value, are NULL and I cant think of why!
$path = "subfolder1","subfolder2","subfolder3"
$excludeF1 = #(C:\temp\parentF1\BackUp\*\subfolder5)
$excludeF2 = #(C:\temp\parentF2\BackUp\*\subfolder5)
$x = 0
while($x -lt $path.Count){
$F1 = Get-ChildItem -Recurse -Path "C:\temp\parentF1\BackUp\$path[$x]" |
Where-Object {$_.FullName -notlike $excludeF1}
$F2 = Get-ChildItem -Recurse -Path "C:\temp\parentF2\BackUp\$path[$x]" |
Where-Object {$_.FullName -notlike $excludeF2}
Compare-Object -ref $F1 -dif $F2 |
Select-Object #{Label="$path[$x]";e={$_.InputObject}},`
#{n="Fundort";e={if($_.SideIndicator -like "=>") {write-output "BackUp F1"}`
elseif($_.SideIndicator -like "<="){Write-Output "BackUp F2"}}} | Out-File dif.txt
$x++
}
start .\dif.txt
also the out-file cmdlet doesnt work but that`s a dif topic
Thanks for any help in advance
This does not work as you expect:
Get-ChildItem ... -Path "C:\temp\parentF1\BackUp\$path[$x]"
When using -Path, PowerShell interprets [...] as part of its own wildcard syntax. Use -LiteralPath to prevent that.
Anything more complex than simple variable name ($var) must be enclosed in a subexpression $(...).
Similar issue:
Select-Object #{Label="$path[$x]"; ...
This can be solved by simply removing the quotation, as $path already contains strings.
Solution:
$F1 = Get-ChildItem -Recurse -LiteralPath "C:\temp\parentF1\BackUp\$($path[$x])" | ...
$F2 = Get-ChildItem -Recurse -LiteralPath "C:\temp\parentF2\BackUp\$($path[$x])" | ...
Compare-Object ... |
Select-Object #{Label=$path[$x]; ...
The above fixes your current code, but your code could be simplified like this to avoid the subexpressions:
foreach($currentPath in $path) {
$F1 = Get-ChildItem -Recurse -Path "C:\temp\parentF1\BackUp\$currentPath" |
Where-Object FullName -notlike $excludeF1
$F2 = Get-ChildItem -Recurse -Path "C:\temp\parentF2\BackUp\$currentPath" |
Where-Object FullName -notlike $excludeF2
Compare-Object -ref $F1 -dif $F2 |
Select-Object #{Label=$currentPath;e={$_.InputObject}},`
#{n="Fundort";e={if($_.SideIndicator -like "=>") {write-output "BackUp F1"}`
elseif($_.SideIndicator -like "<="){Write-Output "BackUp F2"}}} | Out-File dif.txt
}
I compare files inside a folder. In this folder some files are existing in two file formats (filename1.jpg, filename1.bmp, ...) and some files are only existing in one format.
I try to find all files which are only existing in .bmp format and delete them.
The Code I got so far is:
$jpg = Get-ChildItem "C:\..\" -File -Filter *.jpg | ForEach-Object -Process {[System.IO.Path]::GetFileNameWithoutExtension($_)}
$bmp = Get-Childitem "C:\..\" -File -Filter *.bmp | ForEach-Object -Process {[System.IO.Path]::GetFileNameWithoutExtension($_)}
Compare-Object $jpg $bmp | where {$_.SideIndicator -eq "=>"}
This lists me the files I am looking for but I have trouble deleting them. I tried some things like:
Compare-Object $jpg $bmp | where {$_.SideIndicator -eq "=>"} | ForEach-Object {
Remove-Item "C:\..\($_.FullName)"
}
but without any success. Does anyone have a hint how I could solve this?
In your foreach your variable is not a file, it is the result from the compare.
Try this:
$a = Get-ChildItem "D:\a" -File -Filter *.jpg
$b = Get-Childitem "D:\b" -File -Filter *.bmp
Compare-Object $a.BaseName $b.BaseName | where {$_.SideIndicator -eq "=>"} | foreach {
$name = $_.InputObject
$File = $b.Where({$_.BaseName -eq $name})
if ($File.Count -gt 1) {
throw "Conflict, more than one file has the name $name"
} else {
Remove-Item -Path $File.FullName
}
}
I have to filter my results by certain strings and tried to do it with -match and -contains.
-match works if I have just one value to filter, but not with an array.
-contains neither works with one string, nor with a string array.
Why isn't it working with several values? Especially the -contains. Or is there another easy way to solve it?
$Folder = 'C:\Test'
$filterArray = #('2017-05', '2017-08')
$filter = '2017-05'
## test with -MATCH
## working with one match string
Get-ChildItem -Path $Folder -Recurse -Include *.txt |
Where { $_.FullName -match $filter } |
ForEach-Object { $_.FullName }
## NOT working with match string array - no results
Get-ChildItem -Path $Folder -Recurse -Include *.txt |
Where { $_.FullName -match $filterArray } |
ForEach-Object { $_.FullName }
## test with -CONTAINS
## NOT working with one contains string - no results
Get-ChildItem -Path $Folder -Recurse -Include *.txt |
Where { $_.FullName -contains $filter } |
ForEach-Object { $_.FullName }
## NOT working with contains string array- no results
Get-ChildItem -Path $Folder -Recurse -Include *.txt |
Where { $_.FullName -contains $filterArray } |
ForEach-Object { $_.FullName }
Using an array as the second operand for the -match or -contains operators doesn't work. There are basically two approaches you could take:
Build a regular expression from the array and use that with the -match operator:
$pattern = #($filterArray | ForEach-Object {[regex]::Escape($_)}) -join '|'
... | Where-Object { $_.FullName -match $pattern }
This is the preferred approach.
Use a nested Where-Object filter and the String.Contains() method:
... | Where-Object {
$f = $_.FullName
$filterArray | Where-Object { $f.Contains($_) }
}
Why isn't it working with several values?
Because these operators were designed to test against a single argument, plain and simple.
The ability to match against multiple arguments in a single operation would beg the question: "Does the input need to satisfy all or any of the argument conditions"?
If you want to test for a match against any of an array of regex patterns, you can construct a single pattern from them using a non-capturing group, like so:
$filterPattern = '(?:{0})' -f ($filterArray -join '|')
Get-ChildItem -Path $Folder -Recurse -Include *.txt | Where {$_.FullName -match $filterPattern} | ForEach-Object{ $_.FullName }
You can also drop the Where-Object and ForEach-Object loop completely, since PowerShell 3.0 supports property enumeration:
(Get-ChildItem -Path $Folder -Recurse -Include *.txt).FullName -match $filterPattern
How to exclude folders ? Now I hardcode the folder names but i want it to be more flexible.
foreach($file in Get-ChildItem $fileDirectory -Exclude folderA,folderb)
"How to exclude folders ?" , if you mean all folders :
get-childitem "$fileDirectory\\*" -file
but it works only for the first level of $fileDirectory .
This works recursevly :
Get-ChildItem "$fileDirectory\\*" -Recurse | ForEach-Object { if (!($_.PSIsContainer)) { $_}}
or
Get-ChildItem "$fileDirectory\\*" -Recurse | where { !$_.PSisContainer }
You can do this by using the pipeline and a Where-Object filter.
First of all, the idiomatic way to iterate over a group of files in PowerShell is to pipe Get-Childitem to Foreach-Object. So rewriting your command gets:
Get-ChildItem $fileDirectory | foreach {
$file = $_
...
}
The advantage of using the pipeline is that now you can insert other cmdlets in between. Specifically, we use Where-Object to filter the list of files. The filter will pass on a file only if it isn't contained in a given array.
$excludelist = 'folderA', 'folderB'
Get-Childitem $fileDirectory |
where { $excludeList -notcontains $_ } |
foreach {
$file = $_
...
}
If you're going to use this a lot, you can even write a custom filter function to modify the list of files in an arbitrary way before passing to foreach.
filter except($except, $unless = #()) {
if ($except -notcontains $_ -or $unless -contains $_ ){
$_
}
}
$excludelist = 'folderA', 'folderB'
$alwaysInclude = 'folderC', 'folderD'
Get-ChildItem $fileDirectory |
except $excludeList -unless $alwaysInclude |
foreach {
...
}
#dvjz said that -file works only in the first level of a folder, but not recursively. But it seems to work for me.
get-childitem "$fileDirectory\\*" -file -recurse
For future googlers, I have found that files have a property called PSIsContainer which is $true when they are a directory.
A command listing all files in $fileDirectory would be:
foreach ($file in Get-ChildItem $fileDirectory | Where-Object -Property PSIsContainer -eq $false)
{
Write-Host $file.Name
}
Note that -Property is optional for the cmdlet Where-Object.
The simplest way to exclude your folders recursively:
foreach($file in Get-ChildItem $fileDirectory -Exclude {Get-ChildItem folderA},{Get-ChildItem folderB})
Where:
$fileDirectory - search folder
folderA, folderB - excluded folders
I am trying to count the files in all subfolders in a directory and display them in a list.
For instance the following dirtree:
TEST
/VOL01
file.txt
file.pic
/VOL02
/VOL0201
file.nu
/VOL020101
file.jpg
file.erp
file.gif
/VOL03
/VOL0301
file.org
Should give as output:
PS> DirX C:\TEST
Directory Count
----------------------------
VOL01 2
VOL02 0
VOL02/VOL0201 1
VOL02/VOL0201/VOL020101 3
VOL03 0
VOL03/VOL0301 1
I started with the following:
Function DirX($directory)
{
foreach ($file in Get-ChildItem $directory -Recurse)
{
Write-Host $file
}
}
Now I have a question: why is my Function not recursing?
Something like this should work:
dir -recurse | ?{ $_.PSIsContainer } | %{ Write-Host $_.FullName (dir $_.FullName | Measure-Object).Count }
dir -recurse lists all files under current directory and pipes (|) the result to
?{ $_.PSIsContainer } which filters directories only then pipes again the resulting list to
%{ Write-Host $_.FullName (dir $_.FullName | Measure-Object).Count } which is a foreach loop that, for each member of the list ($_) displays the full name and the result of the following expression
(dir $_.FullName | Measure-Object).Count which provides a list of files under the $_.FullName path and counts members through Measure-Object
?{ ... } is an alias for Where-Object
%{ ... } is an alias for foreach
Similar to David's solution this will work in Powershell v3.0 and does not uses aliases in case someone is not familiar with them
Get-ChildItem -Directory | ForEach-Object { Write-Host $_.FullName $(Get-ChildItem $_ | Measure-Object).Count}
Answer Supplement
Based on a comment about keeping with your function and loop structure i provide the following. Note: I do not condone this solution as it is ugly and the built in cmdlets handle this very well. However I like to help so here is an update of your script.
Function DirX($directory)
{
$output = #{}
foreach ($singleDirectory in (Get-ChildItem $directory -Recurse -Directory))
{
$count = 0
foreach($singleFile in Get-ChildItem $singleDirectory.FullName)
{
$count++
}
$output.Add($singleDirectory.FullName,$count)
}
$output | Out-String
}
For each $singleDirectory count all files using $count ( which gets reset before the next sub loop ) and output each finding to a hash table. At the end output the hashtable as a string. In your question you looked like you wanted an object output instead of straight text.
Well, the way you are doing it the entire Get-ChildItem cmdlet needs to complete before the foreach loop can begin iterating. Are you sure you're waiting long enough? If you run that against very large directories (like C:) it is going to take a pretty long time.
Edit: saw you asked earlier for a way to make your function do what you are asking, here you go.
Function DirX($directory)
{
foreach ($file in Get-ChildItem $directory -Recurse -Directory )
{
[pscustomobject] #{
'Directory' = $File.FullName
'Count' = (GCI $File.FullName -Recurse).Count
}
}
}
DirX D:\
The foreach loop only get's directories since that is all we care about, then inside of the loop a custom object is created for each iteration with the full path of the folder and the count of the items inside of the folder.
Also, please note that this will only work in PowerShell 3.0 or newer, since the -directory parameter did not exist in 2.0
Get-ChildItem $rootFolder `
-Recurse -Directory |
Select-Object `
FullName, `
#{Name="FileCount";Expression={(Get-ChildItem $_ -File |
Measure-Object).Count }}
My version - slightly cleaner and dumps content to a file
Original - Recursively count files in subfolders
Second Component - Count items in a folder with PowerShell
$FOLDER_ROOT = "F:\"
$OUTPUT_LOCATION = "F:DLS\OUT.txt"
Function DirX($directory)
{
Remove-Item $OUTPUT_LOCATION
foreach ($singleDirectory in (Get-ChildItem $directory -Recurse -Directory))
{
$count = Get-ChildItem $singleDirectory.FullName -File | Measure-Object | %{$_.Count}
$summary = $singleDirectory.FullName+" "+$count+" "+$singleDirectory.LastAccessTime
Add-Content $OUTPUT_LOCATION $summary
}
}
DirX($FOLDER_ROOT)
I modified David Brabant's solution just a bit so I could evaluate the result:
$FileCounter=gci "$BaseDir" -recurse | ?{ $_.PSIsContainer } | %{ (gci "$($_.FullName)" | Measure-Object).Count }
Write-Host "File Count=$FileCounter"
If($FileCounter -gt 0) {
... take some action...
}