Powershell - Exclude folders in Get-ChildItem - powershell

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

Related

Powershell Get-ChildItem Exclude Default Windows Folders

I want to search for files with .2fa extension on remote computers. I can find the files I want, but it takes a long time to get to the second computer because it scans all windows files.
I tried the -exclude and where arguments but they do not work.
Could you please help me? Thanks.
$ServerList = Import-Csv 'C:\PC.CSV'
$result = foreach ($pc in $ServerList.barkod) {
$exclude = '*ProgramData*','*Program Files*','*Program Files (x86)*','*Windows*'.'*winupdate*'
$sourcepath = 'c$'
Get-ChildItem -Path \\$pc\$sourcepath -Recurse | Where-Object { $_.Name -like "*.2fa" } |
where {$_.name -notin $Exclude}
}
$result
I tried
-Exclude $exclude
-where {$_.name -notin $Exclude}
-exclude doesn't work with subdirectories or -filter:
Get-ChildItem -Path \\$pc\$sourcepath\ -exclude $exclude |
get-childitem -recurse -filter *.2fa
Since you are looking for files with a certain extension, use the -Filter parameter.
This will be the fastest option to search for only .2fa files, disregarding all others. (Filter works on the Name property)
If you want to search the C: drive, you are bound to hit Access Denied exceptions and because to exclude a list of foldernames using post-process with a Where-Object clause,
Get-ChildItem will try and search in these folders you need to apend -ErrorAction SilentlyContinue to the command
$exclude = 'ProgramData','Program Files','Program Files (x86)','Windows'.'winupdate'
# create a regex string you can use with the `-notmatch` operator
# each item will be Regex Escaped and joined together with the OR symbol '|'
$excludeThese = ($exclude | ForEach-Object { [Regex]::Escape($_) }) -join '|'
$ServerList = (Import-Csv 'C:\PC.CSV').barkod
$sourcepath = 'c$'
$result = foreach ($pc in $ServerList) {
Get-ChildItem -Path "\\$pc\$sourcepath" -Filter '*.2fa' -File -Recurse -ErrorAction SilentlyContinue |
Where-Object {$_.DirectoryName -notmatch $excludeThese}
}
$result

My script doesnt work when I change the object property from "LastWriteTime" to "CreationTime", it just deletes everything?

Ive been running around like crazy lately with this script that Im trying to modify it to suit my needs. I recently found out that deleting the files using "LastWriteTime" is not what Im after..
What I need my script to do is to delete the files that are older than 30 days using the "CreationTime" property, the problem is that after I modify the script to use this it deletes the entire folder structure?
How can this small modification change the behavior of the entire script?
This is what Im using:
$limit = (Get-Date).AddDays(-30)
$del30 = "D:\CompanyX_ftp\users"
$ignore = Get-Content "C:\Users\UserX\Documents\Scripts\ignorelist.txt"
Get-ChildItem $del30 -Recurse |
Where-Object {$_.CreationTime -lt $limit } |
Select-Object -ExpandProperty FullName |
Select-String -SimpleMatch -Pattern $ignore -NotMatch |
Select-Object -ExpandProperty Line |
Remove-Item -Recurse
So if I were to replace the "CreationTime" property with "LastWriteTime" the script will run and do what its supposed to but if I use "CreationTime" it just deletes everything under the folder structure including the folders themselves and the paths that its supposed to ignore.
UPDATE: The script is working for me now for the actual deletion of the files but for the script that Im using to just get a report on the actual files that the script will delete is actually including the paths of the ignorelist.txt file?
Please see below script:
$limit = (Get-Date).AddDays(-30)
$del30 = "D:\CompanyX_ftp\users"
#Specify path for ignore-list
$ignore = Get-Content "C:\Users\UserX\Documents\Scripts\ignorelist.txt"
Get-ChildItem $del5 -File -Recurse |
Where-Object {$_.CreationTime -lt $limit } |
Select-Object -ExpandProperty FullName |
Select-String -SimpleMatch -Pattern $ignore -NotMatch |
Select-Object -ExpandProperty Line |
Get-ChildItem -Recurse | Select-Object FullName,CreationTime
ignorelist.txt sample data:
D:\CompanyX_ftp\users\ftp-customerA\Customer Downloads
D:\CompanyX_ftp\users\ftp-customerB\Customer Downloads
D:\CompanyX_ftp\users\ftp-customerC\Customer Downloads
D:\CompanyX_ftp\users\ftp-customerD\Customer Downloads
D:\CompanyX_ftp\users\ftp-customerE\Customer Downloads
D:\CompanyX_ftp\users\ftp-customerF\Customer Downloads
D:\CompanyX_ftp\users\ftp-customerG\Customer Downloads
D:\CompanyX_ftp\users\ftp-customerH\Customer Downloads\
Any ideas on why its including the paths that I have mentioned on the ignorelist.txt? (I will also provide an image for better illustration).
Thanks in advance for any help or guidance with this.
//Lennart
I see two problems with the updated code:
Duplicate recursion. First Get-ChildItem iterates over contents of directory recursively. Later in the pipeline another recursive iteration starts on items returned by the first Get-ChildItem, causing overlap.
When filtering by $ignore, only paths that exactly match against the $ignore paths are being ignored. Paths that are children of items in the ignore list are not ignored.
Here is how I would do this. Create a function Test-IgnoreFile that matches given path against an ignore list, checking if the current path starts with any path in the ignore list. This way child paths are ignored too. This enables us to greatly simplify the pipeline.
Param(
[switch] $ReportOnly
)
# Returns $true if $File.Fullname starts with any path in $Ignore (case-insensitive)
Function Test-IgnoreFile( $File, $Ignore ) {
foreach( $i in $Ignore ) {
if( $File.FullName.StartsWith( $i, [StringComparison]::OrdinalIgnoreCase ) ) {
return $true
}
}
$false
}
$limit = (Get-Date).AddDays(-30)
$del30 = "D:\CompanyX_ftp\users"
$ignore = Get-Content "C:\Users\UserX\Documents\Scripts\ignorelist.txt"
Get-ChildItem $del30 -File -Recurse |
Where-Object { $_.CreationTime -lt $limit -and -not ( Test-IgnoreFile $_ $ignore ) } |
ForEach-Object {
if( $ReportOnly) {
$_ | Select-Object FullName, CreationTime
}
else {
$_ | Remove-Item -Force
}
}

Is there a way to set a path using regular expression?

I'm using PowerShell command prompt and I want to set a location to the folder "Folder". The thing is that folder can be everywhere.
I've tried the command Set-Location and Resolve-Path but I don't get it.
I want to do something like that:
$path = Resolve-Path ".*/Folder"
Set-Location $path
Where the .* can be all the parent folder
Any idea?
Would try this:
Get-ChildItem -Path .\ -Name Folder -Recurse -Depth 10
Hope it helps. BR
Edit (see comments):
$array = Get-ChildItem -Path .\ -Name Ping -Recurse -Depth 10
if($array.Count -eq 0){
#stay
}
if($array.Count -eq 1){
Set-Location $array
}
else{
$array | ForEach-Object{Write-Host $_}
}
Requires PowerShell v3 or higher, you can check using $PSVersionTable.*
$path = (Get-ChildItem . -Recurse -Directory | Where-Object { $_.Name -eq "Folder" }).FullName
This will give you all the directories named Folder in current directory and its subdirectories recursively.
The query, however, can return multiple results so if you want to choose the first one use [0]. Also, to cover the case when the query returns no results, enforce returned object to be an array using #( ... ) and check if it exists:
$f = #(Get-ChildItem . -Recurse -Directory | Where-Object { $_.Name -eq "Folder" })[0].FullName
if ( $f ) {
cd $f
}
else {
# Handle the error
}
cd is alias to Set-Location so you can use whichever you prefer. Important thing to remember is to use FullName property as it contains full path to the folder.
* For PowerShell versions lower than v3, use | Where-Object {$_.PSIsContainer -eq $true} instead of -Directory

Using PowerShell to compare and copy files of a certain type recursively

I am trying to copy a particular file type such as foo.pbo. These pbo's are scatted in a lot of sub-directories below the source folder. In the destination it is a folder with one sub-directory below it.
I was to use powershell to compare the source folder and sub-directories with files of a .pbo extension to the destination folder, but here is the twist only copy the files that are newer.
I have tried breaking it down, so here was my attempt.
$s = Get-ChildItem .\mods -filter *pbo -Recurse
$d = Get-ChildItem .\updates\addons -filter *pbo
Compare-Object -ReferenceObject $s -DifferenceObject $d -Property Name, LastWriteTime | Where-Object { $_.SideIndicator -eq "=>" }
How can I copy the files that are found in the compare that are modified in the multiple source directories to a single directory?
All I get in the compare is just the filenames and date modified, I have no path to reference?
I think Compare-Object is the wrong tool for this. It compares the existence of properties - you have no way of specifying that it should compare the dates.
I suggest iterating over each source object with a foreach loop and checking if it needs to be copied.
$s = Get-ChildItem .\source -Filter *.txt -Recurse
$d = Get-ChildItem .\dest -Filter *.txt
foreach ($file in $s) {
$targetFile = $d | where Name -eq $file.Name
# this copies files which do not exist in the target
if ($file.LastWriteTime -gt $targetFile.LastWriteTime) {
Copy-Item $file.FullName .\dest
}
# this copies only files which exist in the target
if ($targetFile -and $file.LastWriteTime -gt $targetFile.LastWriteTime) {
Copy-Item $file.FullName .\dest
}
}
Assuming the Compare-Object is working properly the data you seek is in InputObject note property. At the end of your Where-Object pipe into Select-Object
Where-Object { $_.SideIndicator -eq "=>" } | Select-Object -ExpandProperty InputObject
That will return System.IO.FileInfo objects just like Get-ChildItem that you can do with what you please.
$old_files = Get-ChildItem "C:\Users\USERNAME\Documents\old_files"
$new_files = Get-ChildItem "C:\Users\USERNAME\Documents\new_files"
foreach($file in $new_files){
if($old_files.Name.Contains($file.Name)){}
else {
Copy-Item $file.FullName "C:\Users\USERNAME\Documents\new_addition"
}
}

Recursively count files in subfolders

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...
}