How to speed up Get-ChildItem in Powershell - powershell

Just wandering how could I speed up the Get-ChildItem in Powershell?
I have the following script to search for a file that created by today and copy it over to another folder.
$fromDirectory = "test_file_*.txt"
$fromDirectory = "c:\sour\"
$toDirectory = "c:\test\"
Get-ChildItem $fromDirectory -Include $fileName -Recurse | Where {$_.LastWriteTime -gt (Get-Date).Date} | Copy-Item -Destination $toDirectory
Due to the folder that I search have 124,553 history files, it's take me age for the search. Does any know how could I improve my script to speed up my search and copy?

Here are some things to try:
First, use Measure-Command {} to get the actual performance:
Measure-Command { Get-ChildItem $fromDirectory -Include $fileName -Recurse | Where {$_.LastWriteTime -gt (Get-Date).Date} | Copy-Item -Destination $toDirectory }
Then, consider removing the '-Recurse' flag, because this is actually going inside every directory and child and child of child. If your target log files are really that scattered, then...
Try using robocopy to match a pattern in the filename and lastwritetime, then use powershell to copy over. You could even use robocopy to do the copying.
It's possible that you just have a huge, slow problem to solve, but try these to see if you can break it down.

This is a well-known feature of NTFS. Microsoft's docs say that the limit for decreasing performance is about 50 000 files in a directory.
If the file names are of very similar, creation of 8dot3 legacy names will start to slow down when there are about 300 000 files. Though you have "only" 120 k files, it's the same order of magnitude.
Some previous questions discuss this issue. Sadly, there is no a single good solution but better hierarchy in directories. The usual tricks are to disable 8dot3 with fsutil and last access date via registry, but those will help only so much.
Can you redesign the directory structure? Moving old files into, say, year-quarter subdirs might keep the main directory clean enough. To find out file's year-quarter, a quick way is like so,
gci | % {
$("{2} => {1}\Q{0:00}" -f [Math]::ceiling( ($_.LastAccessTime.toString('MM'))/3),
$_.LastAccessTime.ToString('yyyy'),
$_.Name
)
}

I would try putting the Get-ChildItem $fromDirectory -Include $fileName -Recurse | Where {$_.LastWriteTime -gt (Get-Date).Date} in an Array and then copying results from the Array.
$GrabFiles =#()
$GrabFiles =#( Get-ChildItem $fromDirectory -Include $fileName -Recurse | Where {$_.LastWriteTime -gt (Get-Date).Date} )
Copy-Item -Path $GrabFiles -Destination $toDirectory }

Related

Powershell script which will search folders with regex and which will delete files older than XX

I need a powershell script ;
it must search some subfolders which folders names are starting with character between 1 and 6 (like 1xxxx or 2xxx)
and using the name of these folders as variable it must look under each folder for the *.XML files which are older than 30 min
and if it finds them it must delete it.
there may be more than one folder at same time, which are providing the same conditions so IMO using an array is a good choice. But I'm always open to other ideas.
Anybody can help me please ?
Basically I was using this before the need changes but now it doesnt help me.
powershell -nologo -command Get-ChildItem -Path C:\geniusopen\inbox\000\ready\processed | Where CreationTime -lt (Get-Date).AddDays(-10) | remove-item
Thank you
You can do something like the following and just remove -WhatIf if you are satisfied with the results:
$Time = (Get-Date).AddMinutes(-30)
Get-ChildItem -Path 'C:\MostCommonLeaf' -Recurse -File -Filter '*.xml' |
Where {$_.CreationTime -lt $Time -and (Split-Path $_.DirectoryName -Leaf) -match '^[1-6]' -and $_.Extension -eq '.xml'} |
Remove-Item -WhatIf
MostCommonLeaf would be the lowest level folder that could start as your root search node. We essentially don't want to traverse directories for nothing.
You could potentially make the script above better if you know more about your directory structure. For example, if it is predictable within the path where the 1xxx folders will be, you can construct the -Path parameter to use the [1-6] range wildcard. -Filter '*.xml' could also return .xmls files for example, so that's why there is additional extension condition in the Where.
Using -Recurse and -Include together generally results in much slower queries. So even if tempted, I would avoid a solution that uses those together.
If there are millions of files/directories, a different command construction could be better. Running Split-Path millions of times could be less efficient than just matching on the directory name, e.g. where {$_.DirectoryName -match '\\[1-6][^\\]*$'}.
I think you are looking for something like this:
$limit = (Get-Date).AddMinutes(-30)
$path = "C:\Users\you\xxx"
$Extension = "*.xml"
Get-ChildItem -Path $path -Filter $Extension -Force | Where-Object {$_.CreationTime -lt $limit} | Remove-Item
I haven't tested it though.
Keep in mind whether you need: $.CreationTime or $.LastWriteTime

Powershell: Move file from one directory to another, based on string within file

Looking around the site and can't seem to find some answers for myself.
I'm looking to write a script, that will enable me to move files from one destination to another, based on the contents within the file.
To get into Specifics
Source Destination - V:\SW\FromSite
Copy to Destination - V:\SW\ToSW
FileType - .txt
String - test
Ideally I'd also like to ONLY have the script search files that begin with 7. These are unique identifiers to a region.
Pulling my hair out a bit trying.
I was using the below, which runs without error, but does nothing.
$DestDir = "V:\SW\FromSite"
$SrcDir = "V:\SW\ToSW"
$SearchString = "test"
gci $SrcDir -filter 7*.txt | select-string $SearchString | select path |
move-item -dest $DestDir -whatif
Here's what I would do, though I'm sure there's a more streamlined way to do it.
$files = gci $SrcDir -filter 7*.txt
$files | %{
if ((select-string -path $_.FullName -pattern $SearchString) -ne $null) {
move-item -path $_.FullName -dest $DestDir
}
}
So did some more messing around and the below is working perfectly for what I need
get-childitem "<SourceFolder>" -filter 7*.txt -
recurse | select-string -list -pattern "test" | move -dest "<DestinationFolder>"
Thanks all for the help
Not sure if I missed something (didn't tried it on my machine) - but as long as you pass the -whatif option, move-item "shows what would happen if the cmdlet runs. The cmdlet is not run."; see https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/move-item.
So probably it would have been sufficient to just remove the -whatif from the initial statement.

PowerShell to copy files to destination's subfolders while excluding certain folders in the destination

So I have danced with this off and on throughout the day and the timeless phrase "There's more than one way to skin a cat" keeps coming to mind so I decided to take to the community.
Scenario:
Source folder "C:\Updates" has 100 files of various extensions. All need to be copied to the sub-folders only of "C:\Prod\" overwriting any duplicates that it may find.
The Caveats:
The sub-folder names (destinations) in "C:\Prod" are quite dynamic and change frequently.
A naming convention is used to determine which sub-folders in the destination need to be excluded when the source files are being copied (to retain the original versions). For ease of explanation lets say any folder names starting with "!stop" should be excluded from the copy process. (!stop* if wildcards considered)
So, here I am wanting the input of those greater than I to tackle this in PS if I'm lucky. I've tinkered with Copy-Item and xcopy today so I'm excited to hear other's input.
Thanks!
-Chris
Give this a shot:
Get-ChildItem -Path C:\Prod -Exclude !stop* -Directory `
| ForEach-Object { Copy-Item -Path C:\Updates\* -Destination $_ -Force }
This grabs each folder (the -Directory switch ensures we only grab folders) in C:\Prod that does not match the filter and pipes it to the ForEach-Object command where we are running the Copy-Item command to copy the files to the directory.
The -Directory switch is not available in every version of PowerShell; I do not know which version it was introduced in off the top of my head. If you have an older version of PowerShell that does not support -Directory then you can use this script:
Get-ChildItem -Path C:\Prod -Exclude !stop* `
| Where-Object { $_.PSIsContainer } `
| ForEach-Object { Copy-Item -Path C:\Updates\* -Destination $_ -Force }
To select only sub folders which do not begin with "!stop" do this
$Source = "C:\Updates\*"
$Dest = "C:\Prod"
$Stop = "^!stop"
$Destinations = GCI -Path $Dest |?{$_.PSIsContainer -and $_.Name -notmatch $Stop }
ForEach ($Destination in $Destinations) {
Copy-Item -Path $Source -Destination $Destination.FullName -Force
}
Edited Now copies all files from Update to subs of Source not beginning with "!stop" The -whatif switch shows what would happen, to arm the script remove the -whatif.
Edit2 Streamlined the script. If also Sub/sub-folders of C:\Prod shall receive copies include a -rec option to the gci just in front of he pipe.

Copy files in PowerShell too slow

Good day, all. New member here and relatively new to PowerShell so I'm having trouble figuring this one out. I have searched for 2 days now but haven't found anything that quite suits my needs.
I need to copy folders created on the current date to another location using mapped drives. These folders live under 5 other folders, based on language.
Folder1\Folder2\Folder3\Folder4\chs, enu, jpn, kor, tha
The folders to be copied all start with the same letters followed by numbers - abc123456789_111. With the following script, I don't need to worry about folder names because only the folder I need will have the current date.
The folders that the abc* folders live in have about 35k files and over 1500 folders each.
I have gotten all of this to work using Get-ChildItem but it is so slow that the developer could manually copy the files by the time the script completes. Here is my script:
GCI -Path $SrcPath -Recurse |
Where {$_.LastWriteTime -ge (Get-Date).Date} |
Copy -Destination {
if ($_.PSIsContainer) {
Join-Path $DestPath $_.Parent.FullName.Substring($SrcPath.length)
} else {
Join-Path $DestPath $_.FullName.Substring($SrcPath.length)
}
} -Force -Recurse
(This only copies to one destination folder at the moment.)
I have also been looking into using cmd /c dir and cmd /c forfiles but haven't been able to work it out. Dir will list the folders but not by date. Forfiles has turned out to be pretty slow, too.
I'm not a developer but I'm trying to learn as much as possible. Any help/suggestions are greatly appreciated.
#BaconBits is right, you have a recurse on your copy-item as well as your getchild-item. This will cause a lot of extra pointless copies which are just overwrites due to your force parameter. Change your script to do a foreach loop and drop the recurse parameter from copy-item
GCI -Path $SrcPath -Recurse |
Where {$_.LastWriteTime -ge (Get-Date).Date} | % {
Copy -Destination {
if ($_.PSIsContainer) {
Join-Path $DestPath $_.Parent.FullName.Substring($SrcPath.length)
} else {
Join-Path $DestPath $_.FullName.Substring($SrcPath.length)
}
} -Force
}

XCOPY deployment script - how to include certain files?

I need to copy only certain parts of a folder using Powershell, specifically this list:
$files = #("MyProgram.exe",
"MyProgram.exe.config",
"MyProgram.pdb",
".\XmlConfig\*.xml")
In human readable form: 3 specific MyProgram.* files under root of target folder and all XML files under XmlConfig folder which itself is under root of source path (..\bin\Release\ in my case). XmlConfig folder must be created in destination, if it does not exist.
What I have tried:
(1) I tried the following, but it did not work, i.e. no folder or files were created at the destination path:
Copy-Item -Recurse -Path "..\bin\Release\" -Destination ".\Test\" -Include $files
(2) When -Include is removed, whole folder structure is successfully created, including subfolders and files:
Copy-Item -Recurse -Path "..\bin\Release\" -Destination ".\Test\"
It must be something wrong with my understanding of how -Include filter works:
(3) I tested an assumption that -Include needs an array of wildcards, but this did not work either:
$files = #("*MyProgram.exe*",
"*MyProgram.exe.config*",
"*MyProgram.pdb*",
"*.\XmlConfig\*.xml*")
Please advise on how to properly do Copy-Item in my case.
UPDATE (based on below answers):
I am looking for a generic implementation that takes an array of strings. It opens the possibility to put all necessary files/paths in one place, for easy editing, so that a non-Powershell knowledgeable person can understand and modify it as required. So in the end it would be single script to perform XCOPY deployments for any project, with input file being the only variable part. For above example, the input would look like this (saved as input.txt and passed as an argument to the main script):
MyProgram.exe
MyProgram.exe.config
MyProgram.pdb
.\XmlConfig\*.xml
I would prefer wildcards approach, since not many people know regex.
i don't know what is wrong with filter but you can still do
$files | % { copy-item ..\bin\release\$_ -Destination .\test}
if you want to preserve directoty structure you'll have to weak this a little, like :
$sourcedir="c:\temp\test"
$f=#("existing.txt","hf.csv";"..\dir2\*.txt")
$f |%{
$source=ls (join-Path $sourcedir $_) |select -expand directoryname
if ("$source" -like "$sourcedir*"){
$destination=$source.Substring($sourcedir.Length)+".\"
}
else{
$destination=$_
}
copy-item $sourcedir\$_ -Destination $destination -WhatIf
}
AFAICT -Include works only with file names or directory names and not combinations i.e. paths. You can try something like this:
$files = 'MyProgram\.exe|MyProgram\.exe\.config|MyProgram\.pdb|XmlConfig\\.*?\.xml'
Get-ChildItem ..\bin\release -r | Where {!$_.PSIsContainer -and ($_.FullName -match $files)} |
Copy-Item -Dest .\test
With wildcards you could do it this way:
$files = #('*MyProgram.exe','*MyProgram.exe.config','*MyProgram.pdb','*\XmkConfig\*.xml')
Get-ChildItem ..\bin\release -r |
Foreach {$fn=$_.Fullname;$_} |
Where {!$_.PSIsContainer -and ($files | Where {$fn -like $_})} |
Copy-Item -Dest .\test