I have an array of folders, called $FolderArray. It contains about 40 folders. Inside each folder are a bunch of txt files. I want to loop through each folder to get the number of files in each folder, as well as the total size of each folder. I got the number of files in each folder to work, but for the folder size, it ends up outputting the file size of the last file in each folder.
I pulled this out of a larger snippet of my code, so if anything needs more clarification please let me know. I appreciate the help!
$ProcessedLocation = "C:\Users\User.Name\Documents"
$FolderArray = gci -Path $ProcessedLocation | Where-Object {$_.PSIsContainer} | Foreach-Object {$_.Name}
Foreach ($i in $FolderArray)
{
$FolderLocation = $ProcessedLocation + $i
[int]$FilesInFolder = 0
Get-ChildItem -Path $FolderLocation -Recurse -Include '*.txt' | % {
$FilesInFolder = $FilesInFolder + 1
$Length = $_.Length
$FolderSize = $FolderSize + $Length
}
Write-Host $FolderSize
}
You are iterating over $FolderArray twice, once in the foreach($i in $FolderArray) loop, and then again inside the loop body:
foreach($i in $FolderArray){
Get-ChildItem $FolderArray # don't do this
}
If you want to look into each folder in $FolderArray individually, reference the current variable (in your example that would be $i).
I would recommend saving the output from Get-ChildItem to a variable and then grab the size and count of the files from that:
# keep folders as DirectoryInfo objects rather than strings
$FolderArray = Get-ChildItem -Path $ProcessedLocation
foreach ($Folder in $FolderArray)
{
# retrieve all *.txt files in $Folder
$TxtFiles = Get-ChildItem -Path $Folder -Recurse -Include '*.txt'
# get the file count
$FilesInFolder = $TxtFiles.Count
# calculate folder size
$FolderSize = ($TxtFiles | Measure -Sum Length).Sum
# write folder size to host
$FolderSizeMB = $FolderSize / 1MB
Write-Host "$Folder is $FolderSizeMB MB in size"
}
Related
I'm trying to populate an array of file paths where ever the script is located in. But I don't want
the array to include the path of the script only the other files in that folder. I have tried removing it after it is populated by using a list array instead but then I get an error that the array is a fixed size.
#To get path in which the script is located
$mypath = $MyInvocation.MyCommand.Path
$myStringPath=$mypath.ToString().Replace("TestingScriptPath.ps1", "")
#Populates files inside the folder
$array = #()
(Get-ChildItem -Path $myStringPath ).FullName |
foreach{
$array += $_
}
#display paths
for($i = 0; $i -lt $array.length; $i++)
{
$array[$i]
}
You're better off not putting it in the array in the first place.
When updating an array the whole array has to be rewritten, so the performance tends to be terrible.
Use a different datatype if you're going to be removing item-by-item.
#To get path in which the script is located
$mypath = $MyInvocation.MyCommand.Path
$myStringPath=$mypath.ToString().Replace("testingscriptpath.ps1", "")
#Populates files inside the folder
$array = Get-ChildItem -Path $myStringPath | Where-Object {$_.fullname -ne $mypath}
$array
if you definitely want to do it the way suggested in the question (slower)
$ArrayWithFile = Get-ChildItem -Path $myStringPath
$ArrayWithoutFile = $ArrayWithFile | Where-Object {$_.fullName -ne $mypath}
I want to rename a some files. It depends on the array of string new name I have. I tried this, but the files is renaming all. My expectation, the file only rename 3 files, because I only has 3 array. Please help anyone can has idea. Thanks a lot.
$Names = #("Student", "Employee", "Married")
$DataFile = Get-ChildItem -Path .\*.txt #There are more than 10 files
foreach ($name in $Names)
{
$DataFile | Rename-Item -NewName {"$name" + $_.Name}
}
For this you will need to created an counter ($i) to index into the other array:
$Names = 'Student', 'Employee', 'Married'
$DataFile = Get-ChildItem -Path .\*.txt
$Names | ForEach-Object { $i = 0 } {
Rename-Item $DataFile[$i] ($_ + $DataFile[$i++].Name)
}
Note that the ForEach-Object supports multiple script blocks:
When you provide multiple script blocks to the Process parameter, the
first script block is always mapped to the begin block. If there are
only two script blocks, the second block is mapped to the process
block. If there are three or more script blocks, first script block is
always mapped to the begin block, the last block is mapped to the end
block, and the blocks in between are all mapped to the process block.
If you only want to rename as many files as there are strings in your $Names array, you can do:
$Names = 'Student', 'Employee', 'Married'
$count = 0
Get-ChildItem -Path .\*.txt | Select-Object -First ($Names.Count) | ForEach-Object {
$_ | Rename-Item -NewName ('{0}-{1}' -f $Names[$count++], $_.Name)
}
I am working on folders that contain many types and sizes of files within them. What I want to do is move files that are not contained in one folder into a new folder. I have embedded a picture link that helps illustrate what I am aiming to do.
I would like test123.pdf to be moved to a new location because it's not contained within the other folder. Below I have some code that simply compares the contents of each folder and outputs which file is out of place. I have been researching some things online, but have come up empty. Can anyone help me proceed?
Disclaimer: I know the path is wrong, but I can't show it for security reasons.
$Folder1 = Get-ChildItem -Recurse -path "Enter Path here"
$Folder2 = Get-ChildItem -Recurse -path "Enter the Path here"
Compare-Object -ReferenceObject $Folder1 -DifferenceObject $Folder2
It sounds like you want to compare the contents of two folders, ID the file(s) that are not present in both folders, and then move them to a third folder. To accomplish this, you can define your 2 paths in variables, compare the contents of the folders, grab the full path names of the different items, and then move them to the new destination.
$path1 = yourfirstpath
$path2 = yoursecondpathforcomparing
$path3 = yourdestinationpath
diff (ls $path1 -recurse) (ls $path2 -recurse) | ForEach {$_.InputObject.FullName} | Move-Item -Destination $path3
diff = Compare-Object , ls = Get-ChildItem
This will do the work, any file that is not in both Folder1 & Folder2 will be moved to Folder 3
$Folder1 = 'C:\folder1'
$Folder2 = 'C:\folder2'
$Folder3 = 'C:\folder3'
foreach ($file in Get-ChildItem -Recurse $Folder1)
{
if (-not(Test-Path "$Folder2\$file"))
{
Move-Item $file.FullName $Folder3
}
}
This will compare by file name, and maintain the directory structure the extra files were found in, and handle the recursion properly. The output of the code demonstrates the directory structure I tested with.
# be explict here to ensure everything is lowercase
# so when this is cut and pasted, it does not break
# when you enter the real path
$d1 = "d:\test\one".ToLower()
$d2 = "d:\test\two".ToLower()
$f1 = gci -Recurse $d1 -File | % {$_.FullName.ToLower()}
$f2 = gci -Recurse $d2 -File | % {$_.FullName.ToLower()}
"f1"
$f1
"`nf2"
$f2
$same = #()
$extra = #()
foreach ($f in $f2)
{
$f2tof1path = $f.Replace($d2, $d1)
if ($f1.Contains($f2tof1path) -eq $false)
{
$extra += $f
}
else {
$same += $f
}
}
"`nSame"
$same
"`nExtra"
$extra
"`nMOVE"
$folder3 = "d:\test\three"
foreach ($f in $extra)
{
# move files somewhere, create dir if not exists
$dest = $f.Replace($d2,$folder3)
$destdir = $(Split-Path -Parent -Path $dest)
if (!(Test-Path $destdir))
{
# remove quotes to do new-item, keep them to show what it will do
"New-Item -ItemType Directory -Force -Path $destdir"
}
# remove quotes to do move-item, keep them to show what it will do
"Move-Item $f $dest"
}
Output
f1
d:\test\one\1.txt
d:\test\one\2.txt
d:\test\one\sub\1.txt
d:\test\one\sub\2.txt
f2
d:\test\two\1.txt
d:\test\two\2.txt
d:\test\two\3.txt
d:\test\two\sub\1.txt
d:\test\two\sub\2.txt
d:\test\two\sub\3.txt
Same
d:\test\two\1.txt
d:\test\two\2.txt
d:\test\two\sub\1.txt
d:\test\two\sub\2.txt
Extra
d:\test\two\3.txt
d:\test\two\sub\3.txt
MOVE
New-Item -ItemType Directory -Force -Path d:\test\three
Move-Item d:\test\two\3.txt d:\test\three\3.txt
New-Item -ItemType Directory -Force -Path d:\test\three\sub
Move-Item d:\test\two\sub\3.txt d:\test\three\sub\3.txt
If this does not solve your problem, it should get you 99% there. Play around with the code, have fun, and good luck.
I have the below Powershell script which tries to archive the logs.
1st step is to move all the files that contain Spotfire.Dxp...* string the ProjectLogsDir directory.
2nd step is identify the Spotfire.Dxp...* files in RotatedLogsDir directory that are older than 60 days and put them in a zip file together in a ArchivedLogsDir directory
3rd step is to delete zipped file older than 120 days.
here 2nd step function isn't working with error
E:\TIBCO\logsArchival\rotatedDir\C0005749_2014-05-09-12-53-47_Spotfire.Dxp.Web.231.log E:\TIBCO\logsArchival\rotatedDir\C0005749_2014-05-09-12-53-47_Spotfire.Dxp.Web.232.log: WARNING: The filename, directory name, or volume label syntax is incorrect.
$sysname=$env:COMPUTERNAME
$Date = Get-Date
$Now = Get-Date -format yyyy-MM-dd-HH-mm-ss
$host_date=$sysname +"_"+ $Now
$RotateDays = "60"
$ArchiveDays="120"
$ProjectLogsDir = "E:\TIBCO\*\6.0.0\Logfiles"
$RotatedLogsDir = "E:\TIBCO\logsArchival\rotatedDir"
$ArchivedLogsDir= "E:\TIBCO\logsArchival\archiveDir"
$psLogsDir= "E:\TIBCO\logsArchival\shLogsDir"
$LastRotate = $Date.AddDays(-$RotateDays)
$LastArchive = $Date.AddDays(-$ArchiveDays)
$RenameLogFiles = Get-Childitem $ProjectLogsDir -include Spotfire.Dxp.*.*.* -exclude spotfire.Dxp.web.KeepAlive.* -Recurse
$RenameLogFiles
$RenameLogFiles | Where-Object {!$_.PSIsContainer} | Rename-Item -NewName { $host_date +"_"+ $_.Name.Replace(' ','_') };
$RotatedLogFiles = Get-Childitem $ProjectLogsDir -include *_Spotfire.Dxp.*.*.* -Recurse
$RotatedLogFiles
$RotatedLogFiles | move-item -destination "$RotatedLogsDir"
$ZippedLogFiles = Get-Childitem $RotatedLogsDir -include *_Spotfire.Dxp.*.*.* -Recurse | Where {$_.LastWriteTime -le "$LastRotate"}
$ZippedLogFiles
function create-7zip([String] $aDirectory, [String] $aZipfile) {
[string]$pathToZipExe = "C:\Program Files\7-zip\7z.exe";
[Array]$arguments = "a", "-tzip", "$aZipfile", "$aDirectory";
& $pathToZipExe $arguments;
}
create-7zip "$ZippedLogFiles" "$ArchivedLogsDir\$host_date.zip"
$ZippedLogFiles | Remove-Item -Force
$DeleteZippedFiles = Get-Childitem $ArchivedLogsDir\*.zip -Recurse | Where {$_.LastWriteTime -le "$LastArchive"}
$DeleteZippedFiles
$DeleteZippedFiles | Remove-Item -Force
$DeletePsFiles = Get-Childitem $psLogsDir\*.log -Recurse | Where {$_.LastWriteTime -le "$LastRotate"}
$DeletePsFiles
$DeletePsFiles | Remove-Item -Force
Please provide the help here to get the files zipped.
The issue is that you are calling your 7-Zip function incorrectly. Look at what the function takes:
function create-7zip([String] $aDirectory, [String] $aZipfile) {
It has 2 strings as parameters. Then look at what you are calling it with:
create-7zip "$ZippedLogFiles" "$ArchivedLogsDir\$host_date.zip"
"$ZippedLogFiles" was defined just before this by running $ZippedLogFiles = Get-Childitem $RotatedLogsDir with a few parameters to filter the results. So that right there is an array of FileInfo objects... not a string. That's the issue, is that you are not calling the function correctly.
What you really want to include is "$RotatedLogsDir\*_Spotfire.Dxp*.*" so try calling it with that instead of "$ZippedLogFiles".
Edit: Comment moved to answer for zipping only files over 60 days.
You can use built in .Net calls to archive things and not have to use 7-Zip. Even easier in my opinion would be to install the PowerShell Community Extensions and use their Write-Zip cmdlet like this:
$ZippedLogFiles = Get-Childitem $RotatedLogsDir -include *_Spotfire.Dxp.*.*.* -Recurse | Where {$_.LastWriteTime -le "$LastRotate"}
$ZippedLogFiles
$ZippedLogFiles | Write-Zip "$ArchivedLogsDir\$host_date.zip"
I have a root directory that consists of many folders and sub folders. I need to check whether a particular file like *.sln or *.designer.vb exists in the folders or subfolders and output the result in a text file.
For Eg:
$root = "C:\Root\"
$FileType = ".sln",".designer.vb"
the text file will have result somewhat like below:
.sln ---> 2 files
.sln files path ---->
c:\Root\Application1\subfolder1\Test.sln
c:\Root\Application2\subfolder1\Test2.sln
Any help will be highly appreciated!
Regards,
Ashish
Try this:
function Get-ExtensionCount {
param(
$Root = "C:\Root\",
$FileType = #(".sln", ".designer.vb"),
$Outfile = "C:\Root\rootext.txt"
)
$output = #()
Foreach ($type in $FileType) {
$files = Get-ChildItem $Root -Filter *$type -Recurse | ? { !$_.PSIsContainer }
$output += "$type ---> $($files.Count) files"
foreach ($file in $files) {
$output += $file.FullName
}
}
$output | Set-Content $Outfile
}
I turned it into a function with your values as default parameter-values. Call it by using
Get-ExtensionCount #for default values
Or
Get-ExtensionCount -Root "d:\test" -FileType ".txt", ".bmp" -Outfile "D:\output.txt"
Output saved to the file ex:
.txt ---> 3 files
D:\Test\as.txt
D:\Test\ddddd.txt
D:\Test\sss.txt
.bmp ---> 2 files
D:\Test\dsadsa.bmp
D:\Test\New Bitmap Image.bmp
To get the all the filecounts at the start, try:
function Get-ExtensionCount {
param(
$Root = "C:\Root\",
$FileType = #(".sln", ".designer.vb"),
$Outfile = "C:\Root\rootext.txt"
)
#Filecount per type
$header = #()
#All the filepaths
$filelist = #()
Foreach ($type in $FileType) {
$files = Get-ChildItem $Root -Filter *$type -Recurse | ? { !$_.PSIsContainer }
$header += "$type ---> $($files.Count) files"
foreach ($file in $files) {
$filelist += $file.FullName
}
}
#Collect to single output
$output = #($header, $filelist)
$output | Set-Content $Outfile
}
Here's a one-liner to determine if at least one file with extension .txt or .ps1 exists in the directory $OutputPath:
(Get-ChildItem -Path $OutputPath -force | Where-Object Extension -in ('.txt','.ps1') | Measure-Object).Count
Explanation: the command tells you the number of files in the specified directory matching any of the listed extensions. You can append -ne 0 to the end, which returns true or false to be used in an if block.
This will search the directory $root and its subdirectories for files of type $FileType, including hidden files and excluding directories:
$root = "C:\Root\";
$FileType = "*.sln", "*.designer.vb";
$results = Get-ChildItem -Path $root -Force -Recurse `
| Where-Object {
if ($_ -isnot [System.IO.DirectoryInfo])
{
foreach ($pattern in $FileType)
{
if ($_.Name -like $pattern)
{
return $true;
}
}
}
return $false;
}
Note that I've modified the strings in $FileType to be formatted as a wildcard pattern. Then group the files by extension:
$resultGroups = $results | Group-Object -Property 'Extension';
Then loop through each group and print the file count and paths:
foreach ($group in $resultGroups)
{
# $group.Count: The number of files with that extension
# $group.Group: A collection of FileInfo objects
# $group.Name: The file extension with leading period
Write-Host "$($group.Name) ---> $($group.Count) files";
Write-Host "$($group.Name) files path ---->";
foreach ($file in $group.Group)
{
Write-Host $file.FullName;
}
}
To write the results to a file instead of the console, use the Out-File cmdlet instead of the Write-Host cmdlet.