How to loop through files (full path) in PowerShell - powershell

How do I do the equivalent in PowerShell? Note that I require the full path to each file.
# ksh
for f in $(find /app/foo -type f -name "*.txt" -mtime +7); do
mv ${f} ${f}.old
done
I played around with Get-ChildItem for a bit and I am sure the answer is there someplace.

I'm not sure what mtime does here is the code to do everything else
gci -re -in *.txt "some\path\to\search" |
?{ -not $_.PSIsContainer } |
%{ mv $_.FullName "$($_.FullName).old" }

This seems to get me close to what I need. I was able to combine some of the information from Jared's answer with this question to figure it out.
foreach($f in $(gci -re -in hoot.txt "C:\temp")) {
mv $f.FullName "$($f.FullName).old"
}
In the interest of sharing the wealth here is my function to simulate *nix find.
function unix-find (
$path,
$name="*.*",
$mtime=0)
{
gci -recurse -include "$name" "$path" |
where-object { -not $_.PSIsContainer -and ($_.LastWriteTime -le (Get-Date).AddDays(-$mtime)) } |
foreach { $_.FullName }
}

Related

Prioritize -Exclude over -Include in Get-ChildItem

I am trying to sanitize my source code into another folder using Powershell:
dir $sourceDir\* -Recurse -Exclude */bin/*,*/obj/* -Include *.sln, *.myapp, *.vb, *.resx, *.settings, *.vbproj, *.ico, *.xml
And it seems like everything is working fine, however, -Include directive sort of whitelists the file before -Exclude, so .XML files under /bin/, for example, are included. I would like -Exclude to take precedence over -Include, so always exclude /bin/ and /obj/ folders in the above script.
It is possible in Powershell, without writing too much code?
You can switch to late filtering to exclude the directories you don't want:
dir $sourceDir\* -Recurse -Include *.sln, *.myapp, *.vb, *.resx, *.settings, *.vbproj, *.ico, *.xml |
where {$_.fullname -notmatch '\\bin\\|\\obj\\'}
Using -like instead of -match:
dir $sourceDir\* -Recurse -Include *.sln, *.myapp, *.vb, *.resx, *.settings, *.vbproj, *.ico, *.xml |
where { ($_.fullname -notlike '*\bin\*') -and ($_.fullname -notlike '*\obj\*') }
Here is my take on it:
param(
$sourceDir="x:\Source",
$targetDir="x:\Target"
)
function like($str,$patterns){
foreach($pattern in $patterns) { if($str -like $pattern) { return $true; } }
return $false;
}
$exclude = #(
"*\bin\*",
"*\obj\*"
);
$include = #(
"*.sln",
"*.myapp",
"*.vb",
"*.resx",
"*.settings",
"*.vbproj",
"*.ico",
"*.xml"
);
dir $sourceDir\* -Recurse -Include $include | where {
!(like $_.fullname $exclude)
}
May not be very Powershell-ish, but it works. I used like function from here.
Any shorter answers are welcome - please go ahead and suggest an alternative.

loop through subfolders

Hi I would like to create a batch file to get all the sub-folders.Can anyone please help me on this?
This is trivial in both batch files or PowerShell:
Batch:
for /r %%x in (*.sql) do (
rem "(or whatever)"
sqlcmd "%%x"
)
PowerShell:
Get-ChildItem -Recurse -Filter *.sql |
ForEach-Object {
sqlcmd $_.FullName # or whatever
}
Here is a powershell script I use to get the size of the "School" folder within the Subfolder of each users' home folders. IE. N:\UserName\UserNameOSXProfile\School
SizeOfSchoolFolder.ps1
$schoolFolderTotalSize=0
$foundChildren = get-childitem *\*OSXProfile\School
foreach($file in $foundChildren){
$schoolFolderTotalSize = $schoolFolderTotalSize + [long](ls -r $file.FullName | measure -s Length).Sum
}
switch($schoolFolderTotalSize) {
{$_ -gt 1GB} {
'{0:0.0} GiB' -f ($_/1GB)
break
}
{$_ -gt 1MB} {
'{0:0.0} MiB' -f ($_/1MB)
break
}
{$_ -gt 1KB} {
'{0:0.0} KiB' -f ($_/1KB)
break
}
default { "$_ bytes" }
}
I pulled the adding numbers from:
Adding up numbers in PowerShell
And the making folder size look pretty:
Get Folder Size from Windows Command Line
And The \*\*OSXProfile\School as a search for a specific sub folder.
Limit Get-ChildItem recursion depth
To replace 'xyz' with 'abc' in all the filenames:
Get-ChildItem -Recurse -Filter *.mp3 | Rename-Item –NewName { $_.name –replace 'xyz','abc' }

powershell how to loop over files and zip them

First time powershell development - have to say not intuitive for a linux scripter.
need to loop over files to zip them and getting errors - anyone have some feedback on a good one liner?
gci C:\temp -r *.csv |
Where-Object { $_.lastwritetime -lt (Get-date).AddDays(-10)} |
ForEach-Object {'c:\temp\bin\gzip.exe' $_.FullName}
You're missing the call (&) operator to actually execute the command in the string. If you don't do this, the string is printed out as the result of an expression evaluation (instead of as a command.)
gci C:\temp -r *.csv `
| Where-Object { ... } `
| ForEach-Object { & 'c:\temp\bin\gzip.exe' $_.FullName}
Incidentally, if you install the PowerShell Community Extensions (http://pscx.codeplex.com) then this becomes much simpler:
ls c:\temp -r *.csv `
| where { ... }
| write-gzip
Applications can be executed in Powershell just by giving their name / path, you don't have to enclose them in quotes as strings and then use iex or &:
c:\temp\bin\gzip.exe $_.FullName
would work for the zipping part.
The following version of your script could come in handy when you can't rely on external tools:
gci C:\temp -recurse *.csv |
Where-Object { $_.lastwritetime -lt (Get-date).AddDays(-10)} |
ForEach-Object {
$zip = $_.fullname -replace "\.csv",".zip";
new-item -type File $zip -force;
((new-object -com shell.application).namespace($zip)).copyhere($_.fullname)
}

Rename files to lowercase in Powershell

I am trying to rename a bunch of files recursively using Powershell 2.0. The directory structure looks like this:
Leaflets
+ HTML
- File1
- File2
...
+ HTMLICONS
+ IMAGES
- Image1
- Image2
- File1
- File2
...
+ RTF
- File1
- File2
...
+ SGML
- File1
- File2
...
I am using the following command:
get-childitem Leaflets -recurse | rename -newname { $_.name.ToLower() }
and it seems to rename the files, but complains about the subdirectories:
Rename-Item : Source and destination path must be different.
I reload the data monthly using robocopy, but the directories do not change, so I can rename them by hand. Is there any way to get get-children to skip the subdirectories (like find Leaflets -type f ...)?
Thanks.
UPDATE: It appears that the problem is with files that are already all lower case. I tried changing the command to:
get-childitem Leaflets -recurse | if ($_.name -ne $_name.ToLower()) rename -newname { $_.name.ToLower() }
but now Powershell complains that if is not a cmdlet, function, etc.
Can I pipe the output of get-childitem to an if statement?
UPDATE 2: This works:
$files=get-childitem Leaflets -recurse
foreach ($file in $files)
{
if ($file.name -ne $file.name.ToLower())
{
rename -newname { $_.name.ToLower() }
}
}
Even though you have already posted your own answer, here is a variation:
dir Leaflets -r | % { if ($_.Name -cne $_.Name.ToLower()) { ren $_.FullName $_.Name.ToLower() } }
Some points:
dir is an alias for Get-ChildItem (and -r is short for -Recurse).
% is an alias for ForEach-Object.
-cne is a case-sensitive comparison. -ne ignores case differences.
$_ is how you reference the current item in the ForEach-Object loop.
ren is an alias for Rename-Item.
FullName is probably preferred as it ensures you will be touching the right file.
If you wanted to excludes directories from being renamed, you could include something like:
if ((! $_.IsPsContainer) -and $_.Name -cne $_.Name.ToLower()) { ... }
Hopefully this is helpful in continuing to learn and explore PowerShell.
Keep in mind that you can pipe directly to Rename-Item and use Scriptblocks with the -NewName parameter (because it also accepts pipeline input) to simplify this task:
Get-ChildItem -r | Where {!$_.PSIsContainer} |
Rename-Item -NewName {$_.FullName.ToLower()}
and with aliases:
gci -r | ?{!$_.PSIsContainer} | rni -New {$_.FullName.ToLower()}
There are many issues with the previous given answers due to the nature of how Rename-Item, Piping, Looping and the Windows Filesystem works. Unfortunatly the the most simple (not using aliases for readability here) solution I found to rename all files and folders inside of a given folder to lower-case is this one:
Get-ChildItem -Path "/Path/To/You/Folder" -Recurse | Where{ $_.Name -cne $_.Name.ToLower() } | ForEach-Object { $tn="$($_.Name)-temp"; $tfn="$($_.FullName)-temp"; $nn=$_.Name.ToLower(); Rename-Item -Path $_.FullName -NewName $tn; Rename-Item -Path $tfn -NewName $nn -Force; Write-Host "New Name: $($nn)";}
slight tweak on this, if you only want to update the names of files of a particular type try this:
get-childitem *.jpg | foreach { if ($_.Name -cne $_.Name.ToLower()) { ren $_.FullName $_.Name.ToLower() } }
this will only lowercase the jpg files within your folder and ignore the rest
You need to temporarily rename them to something else then name them back all lower case.
$items = get-childitem -Directory -Recurse
foreach ($item in $items)
{
if ($item.name -eq $item.name.ToLower())
{
$temp = $item.FullName.ToLower() + "_"
$name = $item.FullName.ToLower()
ren $name $temp
ren $temp $name
}
It's more idomatic in PowerShell to use where instead of if in a pipeline:
gci -Recurse Leaflets |
? { $_.Name -ne $_.Name.ToLower()) } |
% { ren -NewName $_.Name.ToLower() }
A small but important correction to the answer from Jay Bazuzi. The -cne (case sensitive not equal) operator must be used if the where-part should return anything.
Additionally I found that the Path parameter needed to be present. This version worked in my setup:
gci -Recurse |
? { $_.Name -cne $_.Name.ToLower() } |
% { ren $_.Name -NewName $_.Name.Tolower() }
for everyone who is following this thread; the following line can also be used to lower both files and directories.
Get-ChildItem -r | Rename-Item -NewName { $_.Name.ToLower().Insert(0,'_') } -PassThru | Rename-Item -NewName { $_.Name.Substring(1) }
Main post: https://stackoverflow.com/a/70559621/4165074

Exclude directories in PowerShell

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.