Windows PowerShell script and commands to list files and folders modified in past x days excluding specified folders like .git and node_modules

Last updated on: 5 May 2024
Minor update on: 9 Jun. 2024

Quick-Info

ExcludeDirsRecurseListFilesDirs.ps1: Recursively outputs Get-ChildItem returned object for files and directories of current working directory OR user specified directory but excludes folders and their contents present in $ExcludeFolders variable in script. Usage: script-name [optional-path] | next-cmd 
...
The next-cmd is important for making this script useful. An example:
Lists last modified time followed by full path for each folder & file in current directory that was last modifed on 4.05.2024 (4th May) or 5.05.2024 excluding folders whose names are in $ExcludeFolders and such folders' contents
script-name | where { ((get-date('5.05.2024'))-$_.LastWriteTime).days -lt 1 } |  ForEach-Object {"$($_.LastWriteTime) $($_.FullName)"}

=========================================

Below sections are in the form of notes as I was exploring a solution. The bottom of the post covers the solution.

The number of folders and files within .git and node-modules results in long output of Windows commands like:

forfiles /S /C “cmd /c echo @path” /D 30-04-2024

That makes commands like the above unviable for project folders using Git or Node Package Manager (npm).

I should be able to exclude folders like .git and node_modules (and .next for Next.js projects) at least in the output but ideally in the recursion itself. To that end, I did some exploration which was initially unsuccessful but became successful later on. This post captures the notes of my exploration.

=================
Using batch file to list files created or modified in the last 7days
powershell -nop -c "(dir -recurse|where { ((get-date('3.05.2024'))-$_.LastWriteTime).days -lt 2 }).FullName"

...

@echo off
rem start at the top of the tree to visit and loop though each directory
for /r %%a in (.) do (
  rem enter the directory
  pushd %%a
  echo In directory:
  cd
  rem leave the directory
  popd
  )
----
=====================================

Get-ChildItem -Path .
Get-ChildItem -Name .
Get-ChildItem -Path . -Recurse -Force
Get-ChildItem -Path . -Attributes Directory -Recurse -Force

Get-ChildItem -Path .|where { ((get-date('3.05.2024'))-$_.LastWriteTime).days -lt 2 }
Get-ChildItem -Path .|where { ((get-date('4.05.2024'))-$_.LastWriteTime).days -lt 2 }
Get-ChildItem -Path . -Recurse -Force|where { ((get-date('4.05.2024'))-$_.LastWriteTime).days -lt 2 }
Get-ChildItem -Path . -Recurse -Force|where { ((get-date('4.05.2024'))-$_.LastWriteTime).days -lt 1 }

// In commands below, the issue with Exclude is that it only skips the directory but not sub-directories within it (leaves).
// I need to break the recursion when the excluded directory is encountered.
Get-ChildItem -Path . -Attributes Directory -Recurse -Force -Exclude *git
Get-ChildItem -Path . -Attributes Directory -Recurse -Force -Exclude *git,node_modules
// Below command did not exclude .git (and possibly node_modules too). need to investigate further.
// Update: See comments few lines above.
Get-ChildItem -Path . -Recurse -Force -Exclude *git,node_modules|where { ((get-date('4.05.2024'))-$_.LastWriteTime).days -lt 1 }

...

Get-ChildItem -Exclude folder1,folder2 | foreach { Get-ChildItem -Path $_ -Include *.a -Exclude *.b -Recurse | foreach { echo $_ } }
// Below command didn't work
Get-ChildItem -Exclude *git* | foreach { Get-ChildItem -Path $_ -Exclude *git* -Recurse | foreach { echo $_ } }

// Note: I should use '.git' in commands given below instead of 'git'
// Below command works!
Get-ChildItem -Recurse -Name | ? {$_ -notmatch 'git' }

// Below command didn't work
Get-ChildItem -Recurse -Name | ? {$_ -notmatch 'git','node_modules' }

//Works
Get-ChildItem -Recurse -Name | ? {$_ -notmatch 'git' } | ? {$_ -notmatch 'node_modules' }

//Works
Get-ChildItem -Attributes Directory -Recurse -Name | ? {$_ -notmatch 'git' } | ? {$_ -notmatch 'node_modules' } 

// Below command didn't work
Get-ChildItem -Attributes Directory -Recurse -Name | ? {$_ -notmatch 'git' } | ? {$_ -notmatch 'node_modules' } |where { ((get-date('4.05.2024'))-$_.LastWriteTime).days -lt 2 }

// Does not exclude subdirectories of .git and node_modules folders but command runs without syntax error.
Get-ChildItem -Path . -Attributes Directory -Recurse |where { ((get-date('4.05.2024'))-$_.LastWriteTime).days -lt 2 } | ? {$_ -notmatch 'git' } | ? {$_ -notmatch 'node_modules' }

$_ is an alias for $PSItem variable in PowerShell.

...

Hidden folders are skipped by default by Get-ChildItem. When I checked one of my project folders, I saw that .git folder is hidden but not its sub-folders! But Get-ChildItem does not seem to recurse into the .git hidden folder and so its subfolders are not listed. The -force option is used to NOT skip hidden folders.

However, node_modules does not seem to be made a hidden folder by default. Neither is .next folder.

Can making node_modules and .next folder hidden manually be a solution to my needs? Will it trip up something else, especially while editing and running the project app?

...

Another possibility is to use -Depth option to specify the number of levels that the recursion should get into. But I think it may be useful only in some scenarios but is not a general-purpose solution.

...



Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
Above cmd fails as it needs administrator access.

By opening Powershell as administrator, am trying the following command so that only my regular username has execution policy change.
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
Above command succeeded but I still faced a problem running scripts as regular user.
...
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
After running above command,
Get-ExecutionPolicy 
run as regular user, returns "RemoteSigned" whereas earlier it was returning "restricted".
Now I am able to run Powershell scripts.
...
Was able to write my first useful Powershell script in Powershell ISE and run it successfully (from Powershell ISE as well as from Powershell command line):
Get-ChildItem -Path . -Attributes Directory -Recurse | ForEach-Object {Write-Output $_.FullName}
---
The objective of above script was to only print the full path of all directories encountered (excluding hidden directories and perhaps some other special directories too). Using "Get-ChildItem -Path . -Attributes Directory -Recurse " only results in more output which I felt was a distraction.
Powershell ISE helped me get the above script done mainly by prompting me with available members of $_ object when I typed a period after it. This was very useful as it saved me the time of going through the documentation to figure out what object is put out as output of the first command and then what is the property of the object that will give me the full path of the directory.
...
Here's a very interesting article which may help me in a big way:
Getting files and folders recursively in PowerShell…without using -Recurse!, https://cloudrun.co.uk/powershell/getting-files-and-folders-recursively-in-powershell-without-using-recurse/ . As the recursion into subfolders is being done in PowerShell script code instead of using "-Recurse" option with Get-ChildItem, I can explore modifying it to NOT recurse into specific folders like .git, node_modules and .next. 


...

Wrote a couple of PowerShell scripts to recursively list files and directories without using -Recurse parameter for Get-ChildItem based on abovementioned Getting files and folders recursively in PowerShell…without using -Recurse!:
  • RecurseListFilesDirs.ps1: Recursively lists full path of files (in yellow) and directories (in green) of current working directory OR user specified directory using write-host. Usage: script-name [optional-path]
  • RecurseListFilesDirs-WO.ps1: Recursively lists full path of files and directories of current working directory OR user specified directory using write-output. Usage: script-name [optional-path]
...




Check if Array Contains Element in PowerShell, https://java2blog.com/check-if-array-contains-element-powershell/


The Where-Object cmdlet selects objects that have particular property values from the collection of objects that are passed to it. For example, you can use the Where-Object cmdlet to select files that were created after a certain date, events with a particular ID, or computers that use a particular version of Windows.
--- end extract ---

https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-childitem?view=powershell-7.4#outputs informs us that Get-ChildItem cmdlet "outputs these types when accessing the Filesystem drives." with these types being: DirectoryInfo and FileInfo.

[Base class of DirectoryInfo and FileInfo classes:] FileSystemInfo Class, https://learn.microsoft.com/en-us/dotnet/api/system.io.filesysteminfo?view=net-8.0 

PSIsContainer seems to be old stuff. This article states it is old stuff: What’s the difference between PSIsContainer and Get-ChildItem –Directory?, https://www.reddit.com/r/PowerShell/comments/rqilso/whats_the_difference_between_psiscontainer_and/
But it seems to be still available in PowerShell. Following command works:
.\ExcludeDirsRecurseListFilesDirs-WO.ps1 | where {$_.PSIsContainer}

But I could not find PSIsContainer in MS documentation! I mean, it has to be a property of some base class of FileSystemInfo class (which does not seem to have it, itself). Perhaps I did not search properly and should try again, if I need to use PSIsContainer.
...

Wrote a PowerShell script that seems to be the solution for my needs.
ExcludeDirsRecurseListFilesDirs.ps1: Recursively outputs Get-ChildItem returned object for files and directories of current working directory OR user specified directory but excludes folders and their contents present in $ExcludeFolders variable in script. Usage: script-name [optional-path] | next-cmd 

Usage Examples:
Lists last modified time followed by full path for each folder & file in current directory excluding folders whose names are in $ExcludeFolders and such folders' contents
script-name | ForEach-Object {"$($_.LastWriteTime) $($_.FullName)"}

Lists last modified time followed by full path for each folder & file in Test1 directory excluding folders whose names are in $ExcludeFolders and such folders' contents
script-name Test1 | ForEach-Object {"$($_.LastWriteTime) $($_.FullName)"}

Lists last modified time followed by full path for each folder & file in current directory that was last modifed on 4.05.2024 (4th May) or 5.05.2024 excluding folders whose names are in $ExcludeFolders and such folders' contents
script-name | where { ((get-date('5.05.2024'))-$_.LastWriteTime).days -lt 1 } |  ForEach-Object {"$($_.LastWriteTime) $($_.FullName)"}

Lists last modified time followed by full path for each folder & file in current directory that was last modifed in past 1 day i.e. 24 hours excluding folders whose names are in $ExcludeFolders and such folders' contents
script-name | where { ((get-date)-$_.LastWriteTime).days -lt 1 } | ForEach-Object {"$($_.LastWriteTime) $($_.FullName)"}

Lists unique parent path of each folder & file in current directory that was last modifed in past 2 days  excluding folders whose names are in $ExcludeFolders and such folders' contents
script-name | where { ((get-date)-$_.LastWriteTime).days -lt 2 } | ForEach-Object {Split-Path -Parent $_.FullName} | select -unique 


Comments