Powershell Script that recursively searches for specific file and containing directory and copy it to another location - powershell

I'm attempting to write a powershell script that will search recursively for a file and copy it to another location on a local drive with the date appended to it.
However that file could be in multiple different directories. (ex. c:\users\default\Bookmark.txt, c:\users\profile1\Bookmark.txt, c:\users\profile2\Bookmark.txt...etc.)
To distinguish between the different directories I was thinking of appending the directory name containing the file to the filename along with the date. (ex. filename-directoryName-date)
Here is what I have so far:
get-childitem -path "$env:userprofile\AppData\Local\Microsoft\Edge\User Data" -filter Bookmarks -Recurse -ErrorAction SilentlyContinue -Force | copy-item -Destination $env:userprofile\Bookmarks-$(get-date -UFormat %d-%m-%Y)
This works if it only finds 1 copy of the Bookmarks file and it only appends the date.
To figure out the names of the containing folders I used this command.
(get-childitem -path "$env:userprofile\AppData\Local\Microsoft\Edge\User Data" -filter Bookmarks -Recurse -ErrorAction SilentlyContinue -Force).Directory.Name
I need to somehow put this two together and so it outputs :
c:(whateverlocation)\Bookmark-DirectoryName-Date
I hope I'm making sense.

Dan,
When you use the Get-ChildItem command (alias gci) it will return to you an array of DirectoryInfo and FileInfo objects, one for each item it finds. If you are looking for files called "Bookmarks" (or bookmark.txt...can't tell from your examples which one you're looking for) then you can use the following command to get you a list of all of them:
[array]$FileList = gci -Recurse -File -Filter "Bookmarks"
The [array] designation is necessary to be sure the object is an array regardless of how many items are returned. The filter can have wildcards if you don't know the exact filename. What that leaves me with is an array object named $FileList containing all of the information about the files. You can read all about the properties and methods available in these objects at this Microsoft help page.
For this task, we'll need the .FullName property, which tells you the full path & name of each item (which can be used as your source) and the .BaseName & .Extension properties, which give you the filename and extension respectively. Using these properties, you can copy each of the files you find to a destination. Putting it all together, you get something like this:
$SourceFolder = "$($env:userprofile)\AppData\Local\Microsoft\Edge\User Data"
$DestFolder = '' #Path to Destination Folder
[array]$FileList = gci -Path $SourceFolder -Recurse -File -Filter "Bookmarks"
ForEach ($F in $FileList) {
Copy-Item $F.FullName (Join-Path $DestFolder ($F.Name + 'stuff to add to end of filename' + $F.Extension))
}
In this case, since the files are named 'Bookmarks', the $F.Extension should be blank. If using a more traditional filename, the pattern above will help you fit your changes in between the filename and the extension.
Hope this helps you out. If it does, please accept the answer using the check mark on the left.

Related

Use Powershell to list the Fully Pathed Filenames on Individual Separate Lines?

If I execute:
Get-ChildItem *.ext -recurse
the output consists of a series of Directory sections followed by one or more columns of info for each matching file separated by said directory sections. Is there something like the Unix find command? In which each matching file name appears on a single line with its full relative path?
Get-Childitem by default outputs a view for format-table defined in a format xml file somewhere.
get-childitem | format-table
get-childitem | format-list *
shows you the actual properties in the objects being output. See also How to list all properties of a PowerShell object . Then you can pick and choose the ones you want. This would give the full pathname:
get-childitem | select fullname
If you want an output to be just a string and not an object:
get-childitem | select -expand fullname
get-childitem | foreach fullname
Resolve-Path with the -Relative switch can be used to display the relative paths of a set of paths. You can collect the full path names (FullName property) from the Get-ChildItem command and use the member access operator . to grab the path values only.
Resolve-Path -Path (Get-ChildItem -Filter *.ext -Recurse).FullName -Relative
Note: The relative paths here only accurately reflect files found within the current directory (Get-ChildItem -Path .), i.e. Get-ChildItem -Path NotCurrentDirectory could have undesirable results.
Get-ChildItem's -Name switch does what you want:
It outputs the relative paths (possibly including subdir. components) of matching files as strings (type [string]).
# Lists file / dir. paths as *relative paths* (strings).
# (relative to the input dir, which is implicitly the current one here).
Get-ChildItem -Filter *.ext -Recurse -Name
Note that I've used -Filter, which significantly speeds up the traversal.
Caveat: As of PowerShell 7.0, -Name suffers from performance problems and behavioral quirks; see these GitHub issues:
https://github.com/PowerShell/PowerShell/issues/9014
https://github.com/PowerShell/PowerShell/issues/9119
https://github.com/PowerShell/PowerShell/issues/9126
https://github.com/PowerShell/PowerShell/issues/9122
https://github.com/PowerShell/PowerShell/issues/9120
I am having some problem passing the path plus filename to a parser. There are about 90 files of 1 GB each involved in my task. Each of the file is contained in a folder of its own. All of the folders are contained under a parent folder.
Goal: Ideally, I would like to parse 20 files simultaneously for multitasking and continue to the next 20 until all 90 files are done.
This would mean that I would like to spawn some concurrent parsing of 20 files in a batch at any one given time. In carrying out the parsing, I would like to use measure-command to time the work from beginning to finish.
Script I have used:
Get-ChildItem –Path "E:\\OoonaFTP\\input\\Videos3\\" -Filter *.mp4 -recurse | select -expand fullname
Foreach-Object {
Measure-Command { "E:\OoonaFTP\Ooona_x64_ver_2.5.13\OoonaParser.exe -encode -dat -drm $_.FullName" } | Select-Object -Property TotalSeconds
}
===============================
I have this working batch script with a for statement but doing each iteration one after another. This is not what is the ideal case though. I would really like to accomplish this in PowerShell and with simultaneous tasks.
Could someone please suggest some ways by which I could accomplish this?
Thank you very much!
Thanks for the various suggestions. I'm curious that some of them lead to empty output in my Powershell (PSVersion: 5.1.18362.145).
I tried a number of these and, inspired by some of them, found the best answer for my case at the moment:
Get-ChildItem *.ext -recurse | Select-Object -property fullname
(When I made the window wide enough I got all the info I needed; in general I suppose I might need to do more to get the formatting I want.)

Removing Parts of a File Name based on a Delimiter

I have various file names that are categorized in two different ways. They either just have a code like: "866655" or contain a suffix and prefix "eu_866655_001". My hope is to write to a text file the names of files in the same format. I cannot figure out a successful method for removing the suffix and prefix.
Currently this what I have in my loop in Powershell:
$docs = Get-ChildItem -Path $source | Where-Object {$_.Name -match '.doc*'}
if ($docs.basename -contians 'eu_*')
{
Write-Output ([io.fileinfo]"$doc").basename.split("_")
}
I'm hoping to turn "eu_866655_001" into "866655" by using "_" as the delimiter.
I'm aware that the answer is staring me down but I still can't seem to figure it out.
You could do something like the following. Feel free to tweak the -Filter on the Get-ChildItem command.
$source = 'c:\path\*'
$docs = Get-ChildItem -Path $source -File -Filter "*_*_*" -Include '*.doc','*.docx'
$docs | Rename-Item -NewName { "{0}{1}" -f $_.Basename.Split('_')[1],$_.Extension }
The important things to remember is that in order to use the -Include switch, you need an * at the end of the -Path value.
Explanation:
-Filter allows us to filter on names that contain two underscores separating three substrings.
-Include allows us to only list files ending in extensions .docx and .doc.
Rename-Item -NewName supports delayed script binding. This allows us use a scriptblock to perform any necessary operations for each piped object (each file).
Since the target files will always have two underscores, the .Split('_') method will result in an three index array delimited by the _. You have specified that you always want the second delimited substring and that is represented by index 1 ([1]).
The format operator (-f) puts the substring and extension together, completing the file name.

How can I use PowerShell or a cmd "dir" to get the contents of multiple, but similar paths?

For example, I want the contents of the "Last" folder in the structure below. The various path structures are identical except for the first two levels.
C:\zyx-wvu\abc\Level3\Last
C:\tsr-qpo\def\Level3\Last
C:\nml-kji\ghi\Level3\Last
In PowerShell I get close with:
Get-ChildItem -Path C:\*-*\*
...but it doesn't return any results (as in it never finishes) when I try:
Get-ChildItem -Path C:\*-*\*\Level3
Get-ChildItem -Path C:\*-*\*
will only show you what's in the second layer behind anything with a hyphen in c:\
aka it will show
c:\1-2\alpha
c:\1-5\beta
etc...
What you want is
Get-ChildItem -Path C:\*-*\*\*
or more likely you want
Get-ChildItem -Path C:\*-*\* -recurse
if you want to find paths with the SAME name... you could group them together, and pull out anything with more than one finding... you didn't ask very specifically what you wanted, but here's some ideas.
get-childitem -Path c:\*-*\*\* | group-object -property basename | where count -gt 1 | select -expand group

Copying files defined in a list from network location

I'm trying to teach myself enough powershell or batch programming to figure out to achieve the following (I've had a search and looked through a couple hours of Youtube tutorials but can't quite piece it all together to figure out what I need - I don't get Tokens, for example, but they seem necessary in the For loop). Also, not sure if the below is best achieved by robocopy or xcopy.
Task:
Define a list of files to retrieve in a csv (file name will be listed as a 13 digit number, extension will be UNKNOWN, but will usually be .jpg but might occasionally be .png - could this be achieved with a wildcard?)
list would read something like:
9780761189931
9780761189988
9781579657159
For each line in this text file, do:
Search a network folder and all subfolders
If exact filename is found, copy to an arbitrary target (say a new folder created on desktop)
(Not 100% necessary, but nice to have) Once the For loop has completed, output a list of files copied into a text file in the newly created destination folder
I gather that I'll maybe need to do a couple of things first, like define variables for the source and destination folders? I found the below elsewhere but couldn't quite get my head around it.
set src_folder=O:\2017\By_Month\Covers
set dst_folder=c:\Users\%USERNAME&\Desktop\GetCovers
for /f "tokens=*" %%i in (ISBN.txt) DO (
xcopy /K "%src_folder%\%%i" "%dst_folder%"
)
Thanks in advance!
This solution is in powershell, by the way.
To get all subfiles of a folder, use Get-ChildItem and the pipeline, and you can then compare the name to the insides of your CSV (which you can get using import-CSV, by the way).
Get-ChildItem -path $src_folder -recurse | foreach{$_.fullname}
I'd personally then use a function to edit the name as a string, but I know this probably isn't the best way to do it. Create a function outside of the pipeline, and have it return a modified path in such a way that you can continue the previous line like this:
Get-ChildItem -path $src_folder -recurse | foreach{$_.CopyTo (edit-path $_.fullname)}
Where "edit-directory" is your function that takes in the path, and modifies it to return your destination path. Also, you can alternatively use robocopy or xcopy instead of CopyTo, but Copy-Item is a powershell native and doesn't require much string manipulation (which in my experience, the less, the better).
Edit: Here's a function that could do the trick:
function edit-path{
Param([string] $path)
$modified_path = $dst_folder + "\"
$modified_path = $path.substring($src_folder.length)
return $modified_path
}
Edit: Here's how to integrate the importing from CSV, so that the copy only happens to files that are written in the CSV (which I had left out, oops):
$csv = import-csv $CSV_path
Get-ChildItem -path $src_folder -recurse | where-object{$csv -contains $_.name} | foreach{$_.CopyTo (edit-path $_.fullname)}
Note that you have to put the whole CSV path in the $CSV_path variable, and depending on how the contents of that file are written, you may have to use $_.fullname, or other parameters.
This seems like an average enough problem:
$Arr = Import-CSV -Path $CSVPath
Get-ChildItem -Path $Folder -Recurse |
Where-Object -FilterScript { $Arr -contains $PSItem.Name.Substring(0,($PSItem.Length - 4)) } |
ForEach-Object -Process {
Copy-Item -Destination $env:UserProfile\Desktop
$PSItem.Name | Out-File -FilePath $env:UserProfile\Desktop\Results.txt -Append
}
I'm not great with string manipulation so the string bit is a bit confusing, but here's everything spelled out.

How to find folders efficiently with specific name using powershell?

I want to use powershell to search for folders with specific name in some path, I have this:
get-childitem -path $path -Recurse -Directory -filter $folderName |
foreach{
write-host $_.FullName
}
It works but it is very slow, because there are a lot of files to search for. The case I am dealing is that there are huge amount of files inside the folder I want to find itself. It is wasting time to check for all these files. So I am wondering if there is a way to not dig into this folder when the folder name matches what I want to search for. Cannot do it by removing -recurse tag because the folder I want to search is not necessarily just inside $path but maybe some levels down.
Thanks!
Assuming you have access to all folders in the path, you could use Directory.GetDirectories():
$recurse = [System.IO.SearchOption]::AllDirectories
[System.IO.Directory]::GetDirectories($path,$folderName,$recurse)