Advance file search with powershell - powershell

I'm fairly new to Powershell and programming in general. I want to search files using Powershell having multiple conditions. I have managed to write this code
$Drives = Get-PSDrive -PSProvider 'FileSystem'
$Filename= 'Result'
$IncludeExt= '*csv,*docx'
$StartDate= '11/1/20'
$EndDate= '1/26/21'
Get-ChildItem -Path $Drives.Root -Recurse |Where-Object {$IncludeExt -match $_.Extension} | Where-Object { $_.BaseName -match $Filename} | Where-Object {$_.lastwritetime -ge $StartDate -AND $_.lastwritetime -le $EndDate} |
foreach{
$Item = $_.Basename
$Path = $_.FullName
$Type = $_.Extension
$Age = $_.CreationTime
$Path | Select-Object `
#{n="Name";e={$Item}},`
#{n="Created";e={$Age}},`
#{n="filePath";e={$Path}},`
#{n="Folder/File";e={if($Folder){"Folder"}else{$Type}}}`
}| Export-Csv D:\FFNew.csv -NoTypeInformation
This works well when the all variables are mentioned. But how do I get this to work when
Case1: If $Filename is empty then it gives all the files with the mentioned extensions and files modified in Range of dates
Case2: If $IncludeExt is left empty then it gives all files with the $Filename mentioned, currently it gives only the folders and files modified in Range of dates
Case 3: If $Filename and $IncludeExt is left empty it gives all the files modified between the $StartDate and $EndDate

Pranay,
[EDITED]
Ok, here's the revised (exact) script with notes and sample output. Note: you'll have to change the items that are specific to my machine!
$Drives = Get-PSDrive -PSProvider 'FileSystem'
$Filename = "*" #for all or "*partial name*"
$IncludeExt = $Null #for no ext. or "*.csv","*.docx",etc...
$StartDate = '01/1/2020' #to ignore this use 1/1/1920
#For latest date use below otherwise specify date.
$EndDate = (Get-Date).ToShortDateString()
#Note: below uses only 3rd drive in the array remove [2] for all.
$GCIArgs = #{Path = $Drives[2].Root
Recurse = $True
}
If ($Null -ne $IncludeExt) {
$GCIArgs.Add("Include",$IncludeExt)
}
Get-ChildItem #GCIArgs |
Where-Object {($_.BaseName -Like $Filename) -and
($_.lastwritetime -ge $StartDate) -and
($_.lastwritetime -le $EndDate) } |
foreach{
$Item = $_.Basename
$Path = $_.FullName
$Type = $_.Extension
$Type = & {if($_.PSIsContainer){"Folder"}else{$_.Extension}}
$Age = $_.CreationTime
$Path | Select-Object #{n="Name" ;e={$Item}},
#{n="Created" ;e={$Age}} ,
#{n="filePath" ;e={$Path}},
#{n="Folder/File";e={$Type}}
} | Export-Csv -LiteralPath 'G:\BEKDocs\FFNew.csv' -NoTypeInformation
Notes:
$IncludeExt is specified as $Null if it is not used and if used the list is like this ".csv",".docx"
$Filename is specified as "*" for all filenames. Also changed the test from -match to -like so partial filenames should include *, e.g. "partial name".
Notice I changed the location of the check for Extensions to use the -Include parameter of the Get-ChildItem vs checking in the Where-Object.
Changed the piping of data to successive Where-Object clauses and replaced with -and operator, same effect and more efficient.
Changed the test for Directories to use the PSIsContainer property, couldn't see where you were getting the value for $Folder.
Removed the continuation characters from the Select-Object as the comma serves that purpose and is cleaner.
Sample output on Single drive (per code shown above) with some lines hidden for space considerations but notice the last line number.
Sample output on all drives (code edited as per comment in code), again lines hidden for space but showing multiple drives and final line number.
HTH

Related

remove items, except folders in an array

So, i've been scratching my head for a while now and can't seem to figure it out.
I want to delete files and folders older than 'x' days <-- this works fine
I want to delete empty directories left behind <-- this works fine as well
I also want to have some exceptions: filenames and foldernames. The filename exception works fine, but folders don't. There is something strange though. If i put only 1 name in the array of folders i don't want to delete, it works just fine. But if i put multiple in, it suddenly doesn't work anymore?
I have the idea it might be something simple i'm completely missing
$limit = (Get-Date).AddDays(-120)
$path = "C:\Users\user\Documents\files"
$ExcludedFileNames = #("*file1*", "*file2*")
$ExcludedFolders = #("*folder1*", "*folder2*")
# Delete files older than the $limit.
Get-ChildItem -Path $path -Recurse -Force -exclude $ExcludedFileNames |
Where-Object {($_.FullName -notlike $ExcludedFolders) -and (!$_.PSIsContainer) -and ($_.LastWriteTime -lt $limit) } |
Remove-Item -Force
# Delete any empty directories left behind after deleting the old files.
Get-ChildItem -Path $path -Recurse -Force | Where-Object { $_.PSIsContainer -and (Get-ChildItem -Path $_.FullName -Recurse -Force | Where-Object { !$_.PSIsContainer }) -eq $null } | Remove-Item -Force -Recurse
Instead of $.FullName i tried $.Name
Instead of -notlike i tried -notin
I also tried removing the array and put the variables after where-object
I also tried to copy other code from lots of posts but didn't seem to help.
The problem is that -notlike expects a single string as it's right-hand side operand, and so the $ExcludedFolders variable is coerced into the stringvalue "*folder1* *folder2*".
The comparison 'C:\some\path\to\a\folder1\with\a\file.exe' -notlike '*folder1* *folder2*' obviously fails.
You can solve this by using the -notmatch regex operator instead:
$ExcludedFolders = #('folder1', 'folder2') # note that we no longer need the wildcards
# later
... |Where-Object {$_.FullName -notmatch ($ExcludedFolders.ForEach{[regex]::Escape($_)} -join '|') -and (-not $_.PsIsContainer) -and $_.LastWriteTime -lt $limit}
The | is the alternation operator in regex, effectively functioning as an OR
I would use wildcards on the file names to use with the -Exclude parameter, and create a regex string for the foldernames to exclude you can use in the Where-Object clause.
Something like this:
$limit = (Get-Date).AddDays(-120).Date # set to midnight instead of the current time
$path = 'C:\Users\user\Documents\files'
$ExcludedFileNames = '*file1*', '*file2*' # wildcards for the Exclude parameter
$ExcludedFolders = 'folder1','folder2' # can be a partial name, do not use wildcards here
# create a regex string for the folder names to exclude
# each item will be Regex Escaped and joined together with the OR symbol '|'
$FoldersToSkip = ($ExcludedFolders | ForEach-Object { [Regex]::Escape($_) }) -join '|'
# Delete files older than the $limit.
Get-ChildItem -Path $path -File -Recurse -Force -Exclude $ExcludedFileNames |
Where-Object {($_.DirectoryName -notmatch $FoldersToSkip) -and ($_.LastWriteTime -lt $limit) } |
Remove-Item -Force
# Delete any empty directories left behind after deleting the old files.
(Get-ChildItem -Path $path -Recurse -Directory -Force).FullName |
Where-Object { !( Get-ChildItem -Path $_ | Select-Object -First 1 ) } |
Sort-Object -Property Length -Descending |
Remove-Item -Force

PowerShell script speed standalone vs within Visual Code

I have a simple PowerShell script that writes to a file a list of recently added files.
$fpath = "\\DS1821\video\movies"
$file1 = "C:\Users\brian.w.williams\Desktop\RecentMovie.txt"
if (Test-Path $file1) {Remove-Item -Path $file1}
Get-ChildItem -Path "$fpath" -File -Recurse -Include "*.eng.srt" |
ForEach-Object {
$movie = $_.BaseName -replace ".eng",""
if ( ($_.LastWriteTime.Month -ge 7) -and ($_.LastWriteTime.Year -ge 2021) ) {
Write-Host $movie " = " $_.LastWriteTime
Write-Output $movie | Out-file $file1 -append;
}
}
The script works fine. But I noticed that the script runs much faster (a couple of minutes) when run within Visual Code (i.e. "Run without debugging"). When I run the script standalone (i.e. "Run with PowerShell") the script can take hours to complete. Why the difference? Is there anything I can do to speed it up? I have tried mapping the network folder, but that made no difference.
There is improvement to be made to help the code speed up.
First of all, using Write-Host and Write-Output to append to a file inside the ForEach loop is very time consuming.
Then you're using parameter -Include on Get-ChildItem, where you really want to use -Filter. A Filter is MUCH faster than Include, because the latter will only filter the filenames afterwards. -Filter however can only work on one single filename pattern, but this is exactly what you are doing here.
The if(..) can also be improved to have it compare only one value (a datetime) instead of two separate properties from LastWriteTime.
Try:
$sourcePath = '\\DS1821\video\movies'
# set a variable to an output file on your desktop
$outputFile = Join-Path -Path ([Environment]::GetFolderPath("Desktop")) -ChildPath 'RecentMovies.txt'
# set the reference date in a variable for faster comparison later
$refdate = (Get-Date -Year 2021 -Month 7 -Day 1).Date
# get the files, filter on the name ending in '*.eng.srt'
# filter more with Where-Object so you'll get only files newer than or equal to the reference date
# output objects with one calculated property ('MovieName') and the LastWriteTime
$movies = Get-ChildItem -Path $sourcePath -File -Recurse -Filter '*.eng.srt' |
Where-Object { $_.LastWriteTime -ge $refdate } |
Select-Object #{Name = 'MovieName'; Expression = {$_.BaseName -replace '\.eng\.srt$', '.srt'}},
LastWriteTime
# now output what you have collected to file.
# Set-Content creates a new file or overwrites an existing file
$movies.MovieName | Set-Content -Path $outputFile
# and to screen
$movies | ForEach-Object { Write-Host ('{0} = {1}' -f $_.MovieName, $_.LastWriteTime.ToString()) }
# or to Out-GridView if you prefer
$movies | Out-GridView -Title 'My Movies'
1. I've changed some variable names to make the code better readable
2. Since -replace uses regex, you have to escape the dots with a backslash. The regex also anchors the string to the end of the BaseName with the dollar sign ($) at the end

Is there a way in Powershell to not search in multiple folders

I want to search for all files in a PC but I want to exclude some of the folders.
I'm currently using Where-Object { ($_.FullName -notmatch $excludepath) but the problem with this is it looks up in those paths and then filters it. I want my program to not look up in some paths at all because it takes up a lot of time!
Edit: This is the code I'm working with. I want to search a PC for either all the files or with some specific filters like files with specific name, extension and also give the option to exclude a path entirely. This code does all that but while excluding a path it searches in the path and then filters out using Where-Object { ($_.FullName -notmatch $excludepath) . Because C drive is so big I want my program to not look up in the certain multiple path mentioned rather than searching in them and then filtering.
$Filename = "img"
$IncludeExt = "*.jpeg"
$excludepath = "^C:\\Windows" ,"^C:\\Program Files"
$GCIArgs = #{Path = $Drives.Root
Recurse = $True
}
If ($Null -ne $IncludeExt) {
$GCIArgs.Add("Include",$IncludeExt)
}
Get-ChildItem #GCIArgs | Where-Object { ($_.FullName -notmatch $excludepath) -and ($Ignore -notcontains $_.Extension) -and ($_.BaseName -match $Filename )} |
foreach{
$Item = $_.Basename
$Path = $_.FullName
$Type = $_.Extension
$Modified=$_.LastWriteTime
$Age = $_.CreationTime
$Type = &{if($_.PSIsContainer){"Folder"}else{$_.Extension}}
$Path | Select-Object #{n="Name";e={$Item}},
#{n="Created";e={$Age}},
#{n="filePath";e={$Path}},
#{n="Modified";e={$Modified}},
#{n="Folder/File";e={$Type}}
}| Export-Csv D:\SF.csv -NoTypeInformation
try trhis:
$dirtoexclude=#(
'C:\temp\sqldeveloper\sqldeveloper\svnkit\licenses',
'C:\temp\sqldeveloper\sqldeveloper\sqldeveloper\lib' ,
'C:\temp\sqldeveloper\sqldeveloper\sqldeveloper\extensions\oracle.olap',
'C:\temp\sqldeveloper'
)
#method 1 : if you want exclude specific directory
get-childitem "c:\temp" -Recurse | where DirectoryName -notin $dirtoexclude
#method 2 : if you want exclude specific directory and sub directory
get-childitem "c:\temp" -Recurse | foreach{
$Current=$_
#search if current directory element start by one of directory to exclude
$founded=$dirtoexclude | where {$Current.DirectoryName -like "$_*"} | select * -First 1
#not start by directory to exclude, send element to output
if (!$founded)
{
$Current
}
}

Get-ChildItem Where-Object -notlike $array - Is there a way to do this?

I have written a script that will recurse a specified folder and do some analysis on the files within it. I need to exclude specified sub-folders in the analysis. This list of exclusions changes dependent on the base folder being analysed. I have the script working using a long pattern like this:
Get-ChildItem -File -Recurse $source_folder |
Where-Object {
$_.FullName -notlike "*\folder_name0\*" -and
$_.FullName -notlike "*\folder_name1\*" -and
$_.FullName -notlike "*\folder_name2\*" -and
$_.FullName -notlike "*\folder_name3\*" -and
$_.FullName -notlike "*\folder_name4\*"
}
but this is not very reusable. I would like to be able to store exception lists in .CSVs and call the exception list I need based on the folder set I am analyzing. What I would like to do is something like:
$exception_list = Import-CSV .\exception_list
Get-ChildItem -File -Recurse $source_folder |
Where-Object {$_.FullName -notlike $exception_list}
but this does not work. I suspect because I can't specify and 'and' or an 'or' between the elements in the array. I did briefly consider trying to create the whole argument on the fly using a foreach($exception in $exception_list){$argument += "$_.FullName -notlike $exception -and"}, but that got silly and complex pretty quickly since you still have to remove the last 'and'.
Is there an efficient way to do this?
this builds an array of partial names to be excluded, and uses that array to build a regex OR for use in a -notmatch test.
$ExcludedDirList = #(
'PSES-'
'vscode'
'Test_'
)
# regex uses the pipe symbol as the logical "OR"
$RegexExcludedDirList = $ExcludedDirList -join '|'
$Results = Get-ChildItem -Path $env:TEMP -File -Recurse |
Where-Object {
$_.DirectoryName -notmatch $RegexExcludedDirList
}
I really like #lee_dailey's pattern of creating the regex. An alternative method could be to use -in or -notin to compare collections.
Using Pester:
It 'Filters correctly' {
$list = #('fileA', 'file1', 'file2', 'file32')
$filter = #('file1', 'file3')
$expected = #('fileA', 'file2', 'file32')
$list | Where-Object { $_ -notin $filter} | should -be $expected
}
Or just plain comparison operators:
$list = #('fileA', 'file1', 'file2', 'file32')
$filter = #('file1', 'file3')
$expected = #('fileA', 'file2', 'file32')
$newlist = $list | Where-Object { $_ -notin $filter}
(Compare-Object $newlist $expected).length -eq 0
> True

PowerShell Script to add date to folder name in list of home directories saved in variable

I am a PowerShell newbie. I have a .csv file of users that I pulled a list of home directories for using the following:
$hdirpath = Get-Content C:\Temp\UserList.csv | ForEach {Get-ADUser $_ -properties HomeDirectory | Select HomeDirectory}
Output example of the above looks something like this:
HomeDirectory
\servername\Users\roc03\username
\servername\Users\nov01\username
\servername\Users\roc05\username
Now I want to check if a folder exists in each users path and if it exists, i want to add today's date to the end of that folder name. I know there's a If-Exist-Then command that I might be able to use but I'm not sure how to use it with the information saved in $hdirpath.
Any help would be greatly appreciated. Thanks in advance.
Newbie myself so please forgive any mistakes.
From the top of my head I've got this in mind:
foreach ($path in $hdirpath) {
$folders = Get-ChildItem $_ -Recurse | Where-Object {$_.PSIsContainer -eq $True} | Select-Object FullName
$folders | if ($_.Name -eq "Folder Name") {Rename-Item "Folder Name" + Get-Date}
}
Someone will probably correct me, but if they don't give it a go and let me know if you have any problems.
Also - just for future reference, make sure you include code you have tried before posting a question.
EDIT
So, I've now got the following:
$hdirpath = Get-Content C:\temp\dir.txt
$fullDate = Get-Date
$date = $($fullDate.ToShortDateString())
$day = $date -replace "/", "."
foreach ($path in $hdirpath) {
Get-ChildItem -Path $_ -Recurse | Where-Object {$_.PSIsContainer -eq $true} |
Rename-Item -NewName {if ($_.Name.StartsWith('target') -and $_.Name.EndsWith('folder')) {$_.Name + " " + $($day)}}
}
This returns multiple errors saying that it Cannot evaluate parameter 'NewName' because its argument input did not produce any output., but it works and appends the folder name with the current date.
Line 3-5 get the date and format it with "." rather than "\". You can format this however works for you obviously.
My test structure was "C:\temp\roc6\username1" - with multiple subfolders and multiple 'usernameX'. My test folder to re-name was called "targetfolder". End result ended with "targetfolder 08.03.2017".
Revision
This would be my final run of the script - with my limited knowledge this is the best I can do.
$ErrorActionPreference = "SilentlyContinue"
$hdirpath = Get-Content C:\temp\dir.txt
$fullDate = Get-Date
$day = $($fullDate.ToShortDateString()) -replace "/", "."
foreach ($path in $hdirpath) {
Get-ChildItem -Path $_ -Recurse | Where-Object {$_.PSIsContainer -eq $true} |
Rename-Item -NewName {if ($_.Name.StartsWith('target') -and $_.Name.EndsWith('folder')) {$_.Name + " " + $($day)}}
}