Copying files while preserving directory structure - powershell

One of our sites was recently hit by a CryptoLocker infection. Fortunately we caught it early and only have about 8k files encrypted out of the 200k or so on the file-system. Our backups were good too, so I can restore the files
I have a list of all ~8k of the encrypted files in roughly the following format, one file per new line:
\\nas001\DATA\IQC\file.xls
\\nas001\DATA\IQC\file2.xls
\\nas001\DATA\IQC\folder1\file1.xls
\\nas001\DATA\IQC\folder3\file1.xls
I did an ndmp copy of a snapshot of good data, so the backup I am restoring form is another volume on the nas with the same folder structure after a certain point:
\\nas001\IQC_restore\file.xls
\\nas001\IQC_restore\file2.xls
\\nas001\IQC_restore\folder1\file1.xls
\\nas001\IQC_restore\folder3\file1.xls
Is there an easy way with powershell (or really, with batch scripting or robocopy) parse the files with the list of encrypted files and copy only those files from our backup to the original location, overwriting the encrypted files? On another solution I found the following script:
$a = Get-Content "C:\script\hname.txt"
foreach ($i in $a)
{$files= get-content "C:\script\src.txt"
foreach ($file in $files)
{Copy-Item $file -Destination \\$i\C$\temp -force}
}
Which is almost what I need - the foreach $i in $a statement is redundant because I only have one $i, and it's copying all the files listed in the to a single folder rather than copying them in a way to preserve folder structures.
What's the best way to do this? Can I pass it two separate files and tell it to link the two line for line, so for each line in file a it copies the file to the path in file b? Is it easier to pass it one set of files (file a), perform a string replacement, and copy to that location?
Thank you!

I am perhaps making too broad an assumption here - but really the paths between encrypted and restored files are identical as long as you make the appropriate substitution between \\nas001\DATA\IQC and \\nas001\IQC_restore correct? If so....
You can simply take each file from the "known bad files" file (hname.txt?) and substitute the correct path for the destination using the String.Replace method:
$files = Get-Content C:\script\hname.txt
foreach ($file in $files) {
$src = $file.Replace("\\nas001\DATA\IQC","\\nas001\IQC_restore")
Copy-Item $src -Destination $file -Force
}
Or, to be even more brief:
$enc = '\\nas001\DATA\IQC' # encrypted files share/folder
$good = '\\nas001\IQC_restore' # clean share/folder
gc c:\scripts\hname.txt | % { copy $_.replace($enc,$good) -dest $_ -force }
The secret decoder ring:
gc is an alias for the Get-Content cmdlet
% is an alias for the ForEach-Object cmdlet
$_ is a special variable inside a foreach scriptblock that represents the "current item" that is being passed in from the pipeline - in this case each line from the input file hname.txt

Related

Copy subset of files keeping folder structure using Powershell

Looking for some Powershell help with a copying challenge.
I need to copy all MS Office files from a fairly large NAS (over 4 million of them and a little over 5tb) to another drive, retaining the existing folder structure where a file is copied.
I have a text file of all the common Office file types (about 40 of them) - extns.txt
At this stage, being a good StackExchanger, I'd post the script I've got so far, but I've spent best part of a day on this and, not only is what I've got embarrassingly awful, I suspect that even the basic algorithm is wrong.
I started to gci the entire tree on the old NAS, once for each file type
Then I thought it would be better to traverse once and compare every file to the list of valid types.
Then I got into a complete mess about rebuilding the folder structure. I started by splitting on '\' and iterating through the path then wasted an hour of searching because I thought I remembered reading about a simple way to duplicate a path if it doesn't exist.
Another alternative is that I dump out a 4 million line text file of all the files (with full path) I want to copy (this is easy as I imported the entire structure into SQL Server to analyse what was there) and use that as a list of sources
I'm not expecting a 'please write the codez for me' answer but some pointers/thoughts on the best way to approach this would be appreciated.
I'm not sure if this is the best approach, but the below script is a passable solution to the least.
$sourceRootPath = "D:\Source"
$DestFolderPath = "E:\Dest"
$extensions = Get-Content "D:\extns.txt"
# Prefix "*." to items in $extensions if it doesn't already have it
$extensions = $extensions -replace "^\*.|^","*."
$copiedItemsList = New-Object System.Collections.ArrayList
foreach ( $ext in $extensions ) {
$copiedItems = Copy-Item -Path $sourceRootPath -Filter $ext -Destination $DestFolderPath -Container -Recurse -PassThru
$copiedItems | % { $copiedItemsList.Add($_) | Out-Null }
}
$copiedItemsList = $copiedItemsList | select -Unique
# Remove Empty 'Deletable' folders that get created while maintaining the folder structure with Copy-Item cmdlet's Container switch
While ( $DeletableFolders = $copiedItemsList | ? { ((Test-Path $_) -and $_.PSIsContainer -eq $true -and ((gci $_ | select -first 1).Count -eq 0)) } ) {
$DeletableFolders | Remove-Item -Confirm:$false
}
The Copy-Item's -Container switch is going to preserve the folder structure for us. However, we may encounter empty folders with this approach.
So, I'm using an arraylist named $copiedItemsList to add the copied objects into, which I will later use to determine empty 'Deletable' folders which are then removed at the end of the script.
Hope this helps!

Powershell - Read CSV contents, copy file from a network drive to local machine

I am currently in the process of restructuring a Windows server file directory and need to bulk rename over 10,000 files all in different folders but most have duplicate filenames (e.g 0001.pdf, 0002.pdf)
I have written a Powershell script to help me do this but I am having some trouble with the execution.
The script reads a csv file (export of all the files in the folder structure - export.csv) then creates the variables $OldFilename & $NewFileName. The variables are then used to locate the old file location and copy it to a new destination using the Copy-Item command for each row in the CSV.
Code
$csv = import-csv c:\tmp\test.csv
$csv | foreach-object {
$OldFileName =$_.OldFileName
$NewFileName = $_.NewFileName
}
$csv | ForEach-Object {
$OldFileName =$_.OldFileName
$NewFileName = $_.NewFileName
Copy-Item $OldFileName -Destination $NewFileName
}
CSV is formatted as such
OldFileName,NewFileName
"X:\Folder1\0001.pdf","C:\temp\File0001.pdf"
"X:\Folder2\0001.pdf","C:\temp\File0002.pdf"
"X:\Folder3\0001.pdf","C:\temp\File0003.pdf"
Code does not error when run but files do not appear.
A Write-Output displays the correct file path but a Write-Host((Get-Item $file).length/1MB) does not retrieve the files size which leads me to believe there is an issue with the variable!?
Something I need to add?
Many Thanks
Jonny

Powershell pattern matching

I have a requirement to create directories based on a numeric 9 digit with leading zeros primary key from an order entry database on a remote server.
Each morning a new directory should be created. The directory name will be the primary key number of the new customer. I already have the sql statement extracting the primary key information into a text file using BCP that preserves the leading zeros.
This file is transferred from the database server to the local directory where the folders need to be created. Using some PowerShell code that I think I found I am trying to create the folders from the text file that I have been modifying.
I need the leading zeros preserved in the folder name so I can reference back to the database later in the project. My problem is that when I run the PowerShell script, no folders are created. I think I have the problem isolated to the pattern definition, but don't understand what is wrong.
Input txt file example
001132884
001454596
001454602
001454605
001454606
001454601
001107119
001454600
001454608
PowerShell script
$folder="Customerdocuments"; # Directory to place the new folders in.
$txtFile="E:\dirtext.txt"; # File with list of new folder-names
$pattern="\d+.+"; # Pattern that lines must match
Get-Content $txtFile | %{
if($_ -match $pattern)
{
mkdir "$folder\$_";
}
}
I'm missing a clear question/error report.
Your pattern takes all digits (greedy) from input \d+
and requires at least one other (any) character .+ which isn't present in your sample file.
So your issue isn't related to leading zeroes.
Better specify exactly 9 digits and put that into a Where-object,
The path from $folder will be relative to the current folder and should be build using Join-Path
As mkdir is just a wrapper function for New-Item and it supports piped input I'd use it directly.
$folder="Customerdocuments"; # Directory to place the new folders in.
$txtFile="E:\dirtext.txt"; # File with list of new folder-names
$pattern="^\d{9}$" # Pattern that lines must match
Get-Content $txtFile | Where-Object {$_ -match $pattern}|
New-Item -Path {Join-Path $folder $_} -ItemType Directory | Out-Null
}

Add to Array of wildcards in Powershell

I am new to powershell and looking to test something for a POC.
There are multiple files stored on the cloud inbox (File_1.txt, File_2.txt and so on).
I want to add these files using a wildcard to an array and then run some commands (specific to the cloud) for each file.
I cannot specify the -Path in the code as the files are located on cloud and I do not have the path.
The below code works:
$files = #("File_1.txt","File_2.txt","File_3.txt")
foreach ($file in $files) {
Run commands on cloud like...delete inbox/$file
}
However I cannot hard code the file names. I am looking to add file names using wildcard.
$files=#("File*.txt")
foreach ($file in $files) {
Run commands on cloud like...inbox/$file
}
But this does not work as in the log it is taking File*.txt as the name of the file.
Thanks in advance
You should use dir equivalent of PowerShell, Get-ChildItem (In fact dir and ls are aliases for the Cmdlet) to search files using wildcards:
$files = Get-ChildItem file*.txt
foreach($file in $files) {
DoSomeStuffWithFile $file
}
Get-ChildItem returns FileInfo objects. If you want to use the filename String in the loop, you should refer to the Name property of the object:
DoSomeStuffWithFileName $file.Name
You can easily update the file number using string interpolation. Here is a basic example of how this might work:
1 .. 10 |
ForEach-Object {
"hello" | Out-File -Path "File_$_.txt"
}
Here we generate an array of the numbers from 1 to 10 (using the range operator ..), then this is piped to our command (and hence placed in the $_ variable). As each number comes through, the path is constructed with the current value of $_, so you get File_1.txt, File_2.txt, etc.
You can use other looping techniques, but the key is that you can build the filename/path on-the-fly by having PowerShell replace the variables for you.

Renaming files from a text list using powershell

I have seen many different postings here on how to approach this task. I have tried them all with minor variations of them to no avail. Every time I run the powershell code, I still get the same error message: "cannot rename because the file does not exist..."
I just simply want to rename a bunch of files in one folder from the list of filenames I have in a text file.
Here is one version of code:
$inputFile1=#()
$filesTochange=#()
$inputFile1 = get-content H:\dev\outputfile.txt
$filesToChange = Get-ChildItem H:\dev\extractedFiles1 | Foreach -Begin
{$i=0}
-process {Rename-Item $filesToChange[$i] -newName ($inputFile1[$i] -f $i++)
}
Here is another version:
$inputFile1=#()
$filesTochange=#()
$inputFile1 = get-content H:\dev\outputfile.txt
$filesToChange = Get-ChildItem H:\dev\extractedFiles1
$i=0
foreach ($fil in $filesToChange) {Rename-Item $fil -NewName
$inputFile1[$i++]}
Not entirely sure what's your setup or desired output is but give this a whirl bud. Not the most elegant looking solutions but hopefully this is what you are looking for? Do be mindful with the sorting of the filenames in your outputfile.txt and how the folders are listed when you get the childitem.
$BasePath = 'C:\Test'
$FilesToChangeFolderName = 'extractedFiles1'
$filestochange = Get-ChildItem -Path "$BasePath\$FilesToChangeFolderName"
$FileNames = Get-Content "$BasePath\outputfile.txt"
if($filestochange.FullName.Count -eq $FileNames.Count)
{
for($i=0; $i -lt $FileNames.Count; $i++)
{
write-host "Renaming file $($filestochange.Name[$i]) to $($FileNames[$i]+".txt")"
Rename-Item -Path $filestochange.FullName[$i] -NewName ($FileNames[$i]+".txt")
}
}
Setup -
outputfile.txt contains:
renametoA
renametoB
renametoC
renametoD
renametoE
renametoF
Folder structure:
Results:
Renaming file renameto1.txt to renametoA.txt
Renaming file renameto2.txt to renametoB.txt
Renaming file renameto3.txt to renametoC.txt
Renaming file renameto4.txt to renametoD.txt
Renaming file renameto5.txt to renametoE.txt
Renaming file renameto6.txt to renametoF.txt
Explanation [TLDR]:
The script below takes a text file input which contains the name that should be used to rename each text file you have.
Example:
outputfile.txt file contains the names below:
renametoA
renametoB
renametoC
renametoD
renametoE
renametoF
There are 6 text files inside the "extractedFiles1" folder which you can see in the image.
The script actually renames the files in the "extractedFiles1" folder according to the names from the output.txt file.
Thus it follows this logic:
Renaming file renameto1.txt to renametoA.txt
Renaming file renameto2.txt to renametoB.txt
Renaming file renameto3.txt to renametoC.txt
Renaming file renameto4.txt to renametoD.txt
Renaming file renameto5.txt to renametoE.txt
Renaming file renameto6.txt to renametoF.txt
So after all the script runs your "extractedFiles1" folder's gonna look something like this:
Despite this being an older thread, I wanted to offer another "brute force" option.
CodeNagi's reply works well in PowerShell, although it took me a bit to get working.
If you already have a list with file names (output.txt) consider using excel (or OpenOffice) and the command prompt cmd.
This video is a step by step guide for some of this solution:
https://youtu.be/znhqGrF4gVQ
Open cmd with administrator privileges.
Create a list of your current (old) file names:
cd H:\dev\extractedFiles1
dir > input.txt
Open the input.txt (e.g. in Notepad). It should look like this:
Volume in drive H is Windows
Volume Serial Number is C123-4567
Directory of H:\dev\extractedFiles1
05/12/2022 11.24 .
05/12/2022 11.24 ..
05/12/2022 09.34 5,255,248 Filename1.txt
05/12/2022 09.34 5,255,952 Filename2.txt
...
Copy the lines with filenames and timestamps into an Excel sheet
Use Data > Text to columns to split the filenames from the time stamps
Copy or import your target/new filenames from output.txt next to the old filenames
Make a new column with the formula
= "ren"&" "&A1&" "&B1
resulting in something like
ren Filename1.txt FilenameA.txt
Copy all formulas and paste them in cmd. Make sure you are in the right directory.
Notes: If you have spaces in the file names, you will need to wrap each file name first in apostrophes ". Since the concatenation formula in excel doesn't accept """ (triple apostropes), make yourself a column with only " (here C) and then refer to it in the concatenation: = "ren "&C1&A1&C1&" "&C1&B1&C1&.
Further, if you have duplicate files or want to make sure they are copied correclty, you can use the MOVE function instead of rename (ren).
Instead of point 6. above do the following:
Make a new column with the formula
= "MOVE"&" "&A1&" "&"H:\dev\extractedFiles1\Renamed"&B1
Copy the created command into cmd
It will move and rename the files according to the names in B1.
This example make a copy and rename file to a list csv
Import-CSV LISTA.csv -Header newFileName | % { Copy-Item -Path archivo_convert.pdf -Destination "$($_.newfilename).pdf" }