Using Powershell to move documents to a folder based on file name - powershell

I have searched your database for answers and find things that are similar, but not quite what I am needing. I do not know how to code well enough to take some of the examples and modify them to fit my needs.
I have documents that I need to move to a folder that has the same name as the beginning of the document name. Here are a couple of example of my document names (ParcelNum_CountyNum_Year_DocType.pdf)
188777_014_2013_NOAV.pdf
242353_227_2014_HRGNOTICE.pdf
R506275_246_2013_NOAV.pdf
I currently have the documents residing in a single folder named by the county number (the 3 digit number between the first _ and the 2nd _)
I have folders created that are named by the ParcelNum, which is the first set of characters/numbers at the beginning and before the first _. I need Powershell to read the first portion of the document name stopping at the first _ and place it in the folder that matches that numchar.
So for example the first document example above is named 188777_014_2013_NOAV.pdf and resides at the root of the CountyNum folder named 014.
For that example, I would need it to be moved from the root folder 014, into its subfolder named 188777.
I hope that makes sense. Please feel free to ask questions.
Thank you,

Here's what I would do. This is under the assumption that the files you want to move are in a parent folder and the root & sub folders you want to sort them to are also in the same folder, i.e. if the example PDF's path is C:\test\Docs\188777_014_2013_NOAV.pdf and you want it to end up in C:\test\Docs\014\188777.
Update the path in the $basePath variable to your liking. Comments are included above each step with explanations. Enjoy!
# This is the parent directory for the files and sorted folders
$basePath = "C:\test\Docs"
# This sets that folder as your CWD (Current working directory)
Set-Location $basePath
# This grabs the *files* underneath the parent directory, ignoring sub directories
$files = Get-ChildItem $basePath | Where-Object {$_.PSIsContainer -eq $false}
# Starts iterating through each file
foreach ($file in $files) {
# Split the base file name by underscore (creates an array with each part of the name seperated)
$split = $file.BaseName -split "_"
# Store the second item [1] in the array $split as the root folder name
$root = "$basePath\$($split[1])"
# Store the first item [0] in the array $split as the sub folder name
$sub = "$root\$($split[0])"
# Check if the root folder exists, create it if not
if (!(Test-Path $root -ErrorAction SilentlyContinue)) {
New-Item $root -ItemType Directory | Out-Null
}
# Check if the sub folder exists, create it if not
if (!(Test-Path $sub -ErrorAction SilentlyContinue)) {
New-Item $sub -ItemType Directory | Out-Null
}
# Move the file to the sub folder
Move-Item $file.FullName -Destination $sub -Verbose
}

Tried to solve your problem:
#Requires -Version 4.0
function Move-FileToDirectoryWithSamePrefix
{
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true, Position = 0)]
[ValidateScript( {Test-Path $_ -PathType Container})]
[string]
$DirectoryToMoveFileTo,
[Parameter(Mandatory = $true, Position = 1)]
[ValidateScript( {Test-Path $_ -PathType Container})]
[string]
$DirectoryToSearchFiles
)
Begin {
# Needed to find all files for movement
$fileRegex = ".*_[0-9]{3}_[0-9]{4}_.*\.pdf"
$currentLocation = Get-Location
}
Process{
# find files to move
$matchingFile = Get-ChildItem -Path $DirectoryToSearchFiles -Recurse | Where-Object { $_.Name -match $fileRegex }
# Change to destination directory
Set-Location $DirectoryToMoveFileTo
foreach ($file in $matchingFile) {
$directoryName = ($file -split "_")[0]
# Find the director to move to
$dirToMoveFile = Get-ChildItem -Recurse -Include $directoryName
if ($dirToMoveFile -eq $null) {
# Create directory if not existing
$dirToMoveFile = New-Item -Name $directoryName -Path $DirectoryToMoveFileTo -ItemType Directory
}
Move-Item -Path $file.FullName -Destination $dirToMoveFile.fullName
}
}
End{
Set-Location $currentLocation
}
}
Store above in a ps1 file, e.g. moveFile.ps1. Afterwards open a PowerShell and source the file:
PS C:\> . C:\PathToYourPs1File\moveFile.ps1
Afterwards you can the function like:
PS C:\temp> Move-FileToDirectoryWithSamePrefix -DirectoryToMoveFileTo .\filesDestination -DirectoryToSearchFiles .\files
If you need to debug it open the ps1 in the Powershell ISE, set a breakpoint and start the script. For further information see this link.
Hope that helps.

Related

PowerShell search recursively for files and folder which the name contains "..."

I need help to create a script that can save my life.
My backup software got it wrong because I misdesigned my backup plan and I have lots of named files; filename or folder name (2) (encoding conflict).
I would like to recursively search my network share to find folders and files with "(encode conflict)" in their name and first export them for verification.
Then, if all goes well, I would like to move them to another place while keeping the folder's hierarchy and that's where I stuck.
Get-ChildItem -LiteralPath '\\?\E:\Network Shares\Commun' -Recurse -Filter "*(encode*" #| move-item -Destination 'C:\Users\Desktop\Conflits\'
For the export I found the script here :
https://stackoverflow.com/a/15261816/19493679
Thanks to OP
The files from my test are moving without folder's and file's hierarchy ...
Can you help me please ? :)
Move-Item doesn't know about the directory structure, it just gets paths fed one by one from Get-ChildItem. For preserving directory structure, Move-Item would have to know a common base path, but there is currently no way to specify one.
So I've created a helper function New-DestinationPath that can be chained in between Get-ChildItem and Move-Item (or Copy-Item). The function creates the destination directory structure and outputs the source path and the fully resolved destination path.
Function New-DestinationPath {
[CmdletBinding(SupportsShouldProcess)]
param (
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[Alias('Fullname', 'PSPath')]
[String] $Path,
[Parameter(Mandatory, Position=0)]
[String] $CommonPath,
[Parameter(Mandatory, Position=1)]
[String] $Destination
)
process {
# Temporarily change current directory -> base directory for Resolve-Path -Relative
Push-Location $CommonPath
try {
# Resolve input path relative to $CommonPath (current directory)
$relativePath = Resolve-Path $Path -Relative
}
finally {
Pop-Location # Restore current directory
}
# Resolve full target file path and directory
$targetPath = Join-Path $Destination $relativePath
$targetDir = Split-Path $targetPath -Parent
# Create target dir if not already exists (-Force).
$null = New-Item $targetDir -ItemType Directory -Force
# Output the full source and destination paths (for use with Copy-Item or Move-Item)
[PSCustomObject]#{
Path = $Path
Destination = $targetPath
}
}
}
Usage:
$searchPath = '\\?\E:\Network Shares\Commun'
Get-ChildItem -LiteralPath $searchPath -Recurse -File |
Where-Object Fullname -like '*(encode*' |
New-DestinationPath -CommonPath $searchPath -Destination 'C:\Users\Desktop\Conflits' -WhatIf |
Move-Item -LiteralPath { $_.Path } -Destination { $_.Destination } -WhatIf
By using the common parameter -WhatIf the functions only output what they would do. Remove both -WhatIf arguments when you have confirmed that the right paths are used.
In the Move-Item call we are using delay-bind scriptblocks to avoid a ForEach-Object command. Using the automatic variable $_ we can directly refer to the output of the previous command in the pipeline (New-DestinationPath).

Copy a file with its parent directory structure in Powershell

I am trying to create a little patcher in PowerShell which first backs up the files before replacing them. But I can't get it to cooperate when the file is in a subdirectory. To simplify:
First, it creates the backup directory:
$Null = New-Item -Path 'C:\Backup' -ItemType 'Directory'
Next, I check for the file:
[System.IO.FileInfo]$FileInfo = Get-Item -LiteralPath 'C:\App\bin\executable.exe'
Now, I want this file to end up in C:\Backup\bin\executable.exe
The first thing I tried was to get the FullName and replace C:\App with C:\Backup resulting in $FileInfo | Copy-Item -Destination 'C:\Backup\bin\executable.exe'
But it keeps throwing an exception because C:\Backup\bin does not exist. I tried combinations of the -Recurse and -Container switches.
I assume the issue is that I'm setting the full path as my destination, resulting in no relative paths being created. But I don't see another way to get that target path set. I can't tell it to copy to C:\Backup\bin because there's no logical way for me to know about bin without extracting it. At which point I might as well create it in the backup directory.
But I kind of want this to be automagic (for the C:\App\multi\level\path\file.dll at a later stage). This makes the script more flexible if I change the file locations later on.
I'm probably missing something really obvious here. Any suggestions?
This is what I'm doing now, compensating for having to make the parent directories myself. I'm open to ways to optimize and of course still looking for a way to not have to explicitly create the parent directories.
Function Backup-Item {
Param (
[Parameter(Mandatory = $True)] [System.String]$Source,
[Parameter(Mandatory = $True)] [System.String]$SourceRoot,
[Parameter(Mandatory = $False)][System.Management.Automation.SwitchParameter]$Backup = $True,
[Parameter(Mandatory = $False)][System.String]$BackupDirectory
) # Param
Begin {
If (Test-Path -LiteralPath $Source) { [System.IO.FileInfo]$SourceFile = Get-Item -LiteralPath $Source }
Else { } # TODO: Break
If ($Backup) {
If (Test-Path -LiteralPath $BackupDirectory) { } # TODO: Break
Else { $Null = New-Item -Path $BackupDirectory -ItemType 'Directory' }
[System.String]$Destination = $SourceFile.FullName.Replace($SourceRoot,$BackupDirectory)
} # If
} # Begin
Process {
If ($Backup) {
[System.String]$TargetDirectory = $SourceFile.DirectoryName.Replace($SourceRoot,$BackupDirectory)
If (-not (Test-Path -LiteralPath $TargetDirectory)) { $Null = New-Item -Path $TargetDirectory -ItemType 'Directory' }
$SourceFile | Copy-Item -Destination $Destination
} # If
} # Process
End {}
} # Function Backup-Item
I don't like having to provide the SourceRoot but it's the only way to deduce the relative paths I could think of.
I will also need to make BackupDirectory mandatory but only if Backup is True (which is the default and a bit dirty since a switch should be off by default, but I wanted the ability to do -Backup:$False to override).
I would use this as a helper script in the larger patcher-script.
Why not use robocopy to copy the entire C:\Apps folder to the C:\Backup folder, like with
robocopy C:\Apps C:\Backup /MIR
If you want pure PowerShell, you can do
$sourcePath = 'C:\Apps'
$backupPath = 'C:\Backup'
Get-ChildItem $sourcePath -File -Recurse | ForEach-Object {
# create the destination folder, even if it is empty
$targetFolder = Join-Path -Path $backupPath -ChildPath $_.DirectoryName.Substring($sourcePath.Length)
$null = New-Item -ItemType Directory -Path $targetFolder -Force
# next copy the file
$_ | Copy-Item -Destination $targetFolder
}

How to backup these files into specific folders using powershell

I've finally have given up googling and come here out of desperation. Go easy on me I'm fairly new to Powershell.
So, the objective of the code below was to first look through the source folder, then read through each .zip file and move to the directory specified by the value in the hashtable. Unfortunately, this is not how they want it to work anymore.
Now I need to retain the parent folder from source: for example "DAL" and then create the proceeding folders based on the file names and finally move each .zip to its file specified folder. Also, it needs to go through each folder under source which will be at least 20 other folders with a unique 3 character names.
$srcRoot = "C:\Cloud\source\dal"
$dstRoot = "C:\Cloud\Destination"
##$map = #{}; dir -recurse | ? { !$_.psiscontainer} | % { ##$map.add($_.name,$_.PSChildName) }
# DAT and DEV will have to be excluded from folder creation
$map = {
#AEODDAT_201901 = "AEOD\2019\01"
#AEOMDEV_201902 = "AEOM\2019\01"
#AEOYDAT_201902 = "AEOY\2019\01"
}
$fileList = Get-ChildItem -Path $srcRoot -Filter "*.zip*" -File -Force -Recurse
foreach ($file in $fileList)
{
#Go through each file up to mapped string
$key = $file.BaseName.Substring(0,14)
if ($key -in $map.Keys)
{
$fileName = $file.Name
$dstDir = Join-Path -Path $dstRoot -ChildPath $map[$key]
#create direcotory if not in path
if (-not (Test-Path -Path $dstDir))
{
mkdir -Path $dstDir
}
Write-Verbose "Moving $($file.FullName)"
if (Test-Path -Path (Join-Path -Path $dstDir -ChildPath $fileName))
{
#Write error if name exists
Write-Error -Message "File $fileName already exists at $dstDir"
#move path
} else {
Move-Item -Path $($file.FullName) -Destination $dstDir
}
}
}
So C:\Cloud\source\DAL\AEODDAT20190101.zip should create folders in C:\Cloud\Destination\DAL\AEOD\2019\01\AEODDAT20190101.zip would be my desired output.
Welcome, Matt! (no pun intended) One of the habits I have in similar situations with destination folders is to Set-Location $dstRoot and create folders from the relative path. You can execute New-Item with the relative path and the syntax is simpler. For example, your If statement could look like this and it would work the same way (with a slightly different error message):
if ($key -in $map.Keys){
Set-Location $dstRoot
New-Item -ItemType Directory $map[$key] -ErrorAction Ignore #won't raise an error if it exists
Write-Verbose "Moving $($file.FullName)"
#this will raise an error if the file already exists, unless you specify -Force
Move-Item "$($file.FullName)" $map[$key]
}
EDIT: Found 2 issues.
$map is a Hashtable literal that should be preceded with #:
$map = #{
AEODDAT20190101 = "AEOD\2019\01"
You were missing the last character of the base file name by taking only the first 14 characters. AEODDAT2019010 didn't match AEODDAT20190101. This should fix it:
$key = $file.BaseName.Substring(0,15)

How to do looping to rename and read a file using PowerShell?

I have many files in a folder with the same extension file. I want to rename the file one by one then do the other process, which is Proc_After_Rename. In this process, I will read some information of the file. In this process, I want to read the information of the file one by one based on the previous process to rename the extension file name. After I finish do the process, then I pick again the file to rename and do the process.
For now, I can rename the file, but it rename it all the files directly before I do the other process. ANf when I go to this process Proc_After_Rename, I read the information for all the file, because all the file already rename the extension. Anyone can help, please
UPDATED
Function Proc_After_Rename
{
$Path = "C:\Users\SS\PowerShell\"
Write-Host "Do some process with .pro file"
$Job_Info = Get-ChildItem -Path "$store\*.ini" -File -Force
& $Path\UIni.exe $Job_Info AGM CRM AGM_CUR_CRM AGM_CUR_CRM.CMD #this how I read the .ini file
start-sleep -s 1
$Read_AGM_CUR_CRM = Get-Content .\AGM_CUR_CRM.CMD
$a_AGM_CUR_CRM,$b_AGM_CUR_CRM = $Read_AGM_CUR_CRM -split "="
$b_AGM_CUR_CRM
Pick_file
}
Function Pick_file
{
$WKFD= "C:\Users\SS\PowerShell\"
$store = "$WKFD\GM"
$files = #(Get-ChildItem -Path "$store\*.txt")
Foreach ($file in $files)
{
# Check file existence
if (Test-Path -Path $file -PathType Leaf)
{
# Get file name from object path file $file
$file_name = #(Get-ChildItem -Path "$file" -Name)
# Replace the .cue with .pro
$new_name = $file_name -replace ".txt", ".ini"
# Rename the file
Rename-Item -Path $file -NewName "$new_name"
}
Proc_After_Rename
}
}
$A = Pick_file
With the Get-ChildItem cmdlet, you can iterate the results easily by directly piping them through to a Foreach-Object. Inside that loop, every file found is a FileInfo object, represented by the automatic variable $_.
Using the -Filter parameter the below code gets only files with a *.txt extension and by adding the -File switch you only recieve FileInfo objects, not Directory objects.
If I understand the question correctly, you want to first rename each *.txt file to *.ini and then do some more stuff with the renamed file. This should do it:
$store = "C:\Users\HH"
Get-ChildItem -Path $store -Filter '*.txt' -File | ForEach-Object {
# the automatic variable '$_' here represents a single FileInfo object in the list.
# you don't need to test if the file exists, if it doesn't, Get-ChildItem would not return it.
# create the new name for the file. Simply change the extension to '.ini'
$newName = '{0}.ini' -f $_.BaseName
# rename the file and get a reference to it using the -PassThru parameter
$renamedFile = $_ | Rename-Item -NewName $newName -PassThru
# for testing/proof:
# remember that the '$_' variable now has old file name info.
Write-Host ("File '{0}' is now renamed to '{1}'" -f $_.FullName, $renamedFile.FullName)
# now do the rest of your processing, using the $renamedFile FileInfo object.
# you can see what properties and methods a FileInfo object has here:
# https://learn.microsoft.com/en-us/dotnet/api/system.io.fileinfo?view=netframework-4.8#properties
# to get the full path and filename for instance, use $renamedFile.FullName
# ........ #
}
Hope that helps
# Rename the file
Rename-Item -Path $file -NewName "$new_name"
# path of the renamed file
$new_path_file = "$store\$new_name"
# This is the process after rename the file
# ........ #
#Put your process here and make sure you reference the new file, as long as its in
#the foreach you are good.
}
}
One problem with your code is the Get-ChildItem inside Proc_After_Rename. This presents UIni with a list of files instead of a file. I have tried to fix this problem by reworking your code, and sliding part of Proc_After_Rename into Pick_File. I haven't tested any of this, but I hope it gives you a better idea of how to organize your code.
If I were writing this from scratch, I would use pipelines.
Function Pick_file
{
$WKFD= "C:\Users\SS\PowerShell\"
$store = "$WKFD\GM"
$files = #(Get-ChildItem -Path "$store\*.txt")
Foreach ($file in $files)
{
# Check file existence
if (Test-Path -Path $file -PathType Leaf)
{
# Get file name from object path file $file
$file_name = #(Get-ChildItem -Path "$file" -Name)
# Replace the .cue with .pro
$new_name = $file_name -replace ".txt", ".ini"
# Rename the file
Rename-Item -Path $file -NewName "$new_name"
$new_file_name = $file.fullname
& $Path\UIni.exe $new_file_name AGM CRM AGM_CUR_CRM AGM_CUR_CRM.CMD
#this how I read the .ini file
start-sleep -s 1
$Read_AGM_CUR_CRM = Get-Content .\AGM_CUR_CRM.CMD
$a_AGM_CUR_CRM,$b_AGM_CUR_CRM = $Read_AGM_CUR_CRM -split "="
$b_AGM_CUR_CRM
}
}
}
$A = Pick_file

Move files into year/month folders based on file name timestamp powershell

I have thousands of files spanning 5 years which I would like to move into year/month folders. The file names all end with
_yyyy_mm_dd_wxyz.dat
I'm looking for ideas on how I can generate such file folders and move the files into the appropriate folders yyyy/mm using the windows command shell.
You'll need a Regular Expression with (capture groups) to extract year/month from the filename.
Assuming the year/month folder should be placed directly in files parent location.
untested with -Version 2
## Q:\Test\2018\07\23\SO_51485727.ps1
Push-Location 'x:\folder\to\start'
Get-ChildItem *_*_*_*_*.dat |
Where-Object {$_.BaseName -match '_(\d{4})_(\d{2})_\d{2}_[a-z]+$'} | ForEach-Object {
$TargetDir = "{0}\{1}" -f $Matches[1],$Matches[2]
if (!(Test-Path $TargetDir)){MD $TargetDir | Out-Null}
$_ | Move -Destination $TargetDir
}
Sample tree /f after running the script on my ramdriive:
PS A:\> tree /F
A:.
├───2017
│ └───07
│ test_2017_07_24_xyz.dat
└───2018
└───07
test_2018_07_24_xyz.dat
I have created this little quick and dirty script.
Things have been put in more variables than strictly needed, they could be combined in a single line, but I feel this adds clarity which I hope help you understand what happens.
As a note, I have used the date the item was last written to (created or edited).
If you want only the date the file was created and not the time the file was last edited, you could change LastWriteTime to CreationTime
#Load all files from the folder you wish to move on
$items = Get-ChildItem -Path "C:\SomeFolder\RestofPathToYourFiles"
foreach($item in $items) {
#Creates variables for year, month and day
$FolderYear = "$($item.LastWriteTime.Year)"
$FolderMonth = "$($item.LastWriteTime.Month)"
$FolderDay = "$($item.LastWriteTime.Day)"
#create variable with the new directory path
$NewPath = $item.Directory.FullName + "\" + $FolderYear + "\" + $FolderMonth + "\" + $FolderDay
#create variable with the new full path of the file
$NewFullPath = $NewPath + "\" + $item.Name
#test if the folder already is created, if not, create it
if((Test-Path -Path $NewPath) -eq $false) {
New-Item -Force -path $NewPath -Type Directory
}
#move the item to the new folder
Move-Item -Path $item.FullName -Destination $NewFullPath -Force
}
At the simplest, I'd do something like the following:
Determine the year and month related to a file
See if a folder exists already. If not, create it
Move the file
Example...
foreach ($file in $(ls .\stuff.txt)) {
$m = $file.LastWriteTime.Month.ToString("00")
$y = $file.LastWriteTime.Year
$dir = "{0}-{1}" -f $y, $m
New-Item -Name $dir -ItemType directory -ErrorAction SilentlyContinue | Out-Null
Move-Item -Path $file.Fullname -Destination $dir
}