I'm looking if there's a PS command that'd be equal to mv --backup=numbered, and can't find anything.
In essence, move 'file' to 'file.old', but if 'file.old' exists, 'file' should be moved to 'file.old.2'.
For now the closest I found is from this link: https://www.pdq.com/blog/copy-individual-files-and-rename-duplicates/:
$SourceFile = "C:\Temp\File.txt"
$DestinationFile = "C:\Temp\NonexistentDirectory\File.txt"
If (Test-Path $DestinationFile) {
$i = 0
While (Test-Path $DestinationFile) {
$i += 1
$DestinationFile = "C:\Temp\NonexistentDirectory\File$i.txt"
}
} Else {
New-Item -ItemType File -Path $DestinationFile -Force
}
Copy-Item -Path $SourceFile -Destination $DestinationFile -Force
It seems quite awful to have this amount of code. Is there anything simpler available?
Indeed there is no built-in function to do that. However, it should not be a problem to use a function of your own for that purpose.
How about this:
function Copy-FileNumbered {
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true, Position = 0)]
[ValidateScript({Test-Path -Path $_ -PathType Leaf})]
[string]$SourceFile,
[Parameter(Mandatory = $true, Position = 1)]
[string]$DestinationFile
)
# get the directory of the destination file and create if it does not exist
$directory = Split-Path -Path $DestinationFile -Parent
if (!(Test-Path -Path $directory -PathType Container)) {
New-Item -Path $directory -ItemType 'Directory' -Force
}
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($DestinationFile)
$extension = [System.IO.Path]::GetExtension($DestinationFile) # this includes the dot
$allFiles = Get-ChildItem $directory | Where-Object {$_.PSIsContainer -eq $false} | Foreach-Object {$_.Name}
$newFile = $baseName + $extension
$count = 1
while ($allFiles -contains $newFile) {
$newFile = "{0}({1}){2}" -f $baseName, $count, $extension
$count++
}
Copy-Item -Path $SourceFile -Destination (Join-Path $directory $newFile) -Force
}
This will create a new file in the destination like File(1).txt
Of course, if you rather have names like File.2.txt, just change the format template "{0}({1}){2}" to "{0}.{1}{2}"
Use the function like
$SourceFile = "C:\Temp\File.txt"
$DestinationFile = "C:\Temp\NonexistentDirectory\File.txt"
Copy-FileNumbered -SourceFile $SourceFile -DestinationFile $DestinationFile
Related
I am trying to copy all files in folders and sub-folders not older than 300 minutes, but the code I got working only copies the files in the main folder, it doesn't copy the files in subfolders.
At the destination I don't want to maintain the folder structure of the original files, I just want to put all the origin files into a single specific destination folder.
This is the code I have:
Powershell -NoL -NoP -C "&{$ts=New-TimeSpan -M 300;"^
"Get-ChildItem "C:\Origin" -Filter '*.dat'|?{"^
"$_.LastWriteTime -gt ((Get-Date)-$ts)}|"^
%%{Copy-Item $_.FullName 'C:\Destination'}}"
Could someone help me out please?
Thanks in advance.
Here's a modified script for you you can save as "Copy-Unique.ps1" you can run from a batch file.
function Copy-Unique {
# Copies files to a destination. If a file with the same name already exists in the destination,
# the function will create a unique filename by appending '(x)' after the name, but before the extension.
# The 'x' is a numeric sequence value.
[CmdletBinding(SupportsShouldProcess)] # add support for -WhatIf switch
Param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
[Alias("Path")]
[ValidateScript({Test-Path -Path $_ -PathType Container})]
[string]$SourceFolder,
[Parameter(Mandatory = $true, Position = 1)]
[string]$DestinationFolder,
[Parameter(Mandatory = $false)]
[int]$NewerThanMinutes = -1,
[Parameter(Mandatory = $false)]
[string]$Filter = '*',
[switch]$Recurse
)
# create the destination path if it does not exist
if (!(Test-Path -Path $DestinationFolder -PathType Container)) {
Write-Verbose "Creating folder '$DestinationFolder'"
$null = New-Item -Path $DestinationFolder -ItemType 'Directory' -Force
}
# get a list of file FullNames in this source folder
$sourceFiles = #(Get-ChildItem -Path $SourceFolder -Filter $Filter -File -Recurse:$Recurse)
# if you want only files not older than x minutes, apply an extra filter
if ($NewerThanMinutes -gt 0) {
$sourceFiles = #($sourceFiles | Where-Object { $_.LastWriteTime -gt (Get-Date).AddMinutes(-$NewerThanMinutes) })
}
foreach ($file in $sourceFiles) {
# get an array of all filenames (names only) of the files with a similar name already present in the destination folder
$destFiles = #((Get-ChildItem $DestinationFolder -File -Filter "$($file.BaseName)*$($file.Extension)").Name)
# for PowerShell version < 3.0 use this
# $destFiles = #(Get-ChildItem $DestinationFolder -Filter "$baseName*$extension" | Where-Object { !($_.PSIsContainer) } | Select-Object -ExpandProperty Name)
# construct the new filename
$newName = $file.Name
$count = 1
while ($destFiles -contains $newName) {
$newName = "{0}({1}){2}" -f $file.BaseName, $count++, $file.Extension
}
# use Join-Path to create a FullName for the file
$newFile = Join-Path -Path $DestinationFolder -ChildPath $newName
Write-Verbose "Copying '$($file.FullName)' as '$newFile'"
$file | Copy-Item -Destination $newFile -Force
}
}
# you can change the folder paths, file pattern to filter etc. here
$destFolder = Join-Path -Path 'C:\Destination' -ChildPath ('{0:yyyy-MM-dd_HH-mm}' -f (Get-Date))
Copy-Unique -SourceFolder "C:\Origin" -DestinationFolder $destFolder -Filter '*.dat' -Recurse -NewerThanMinutes 300
Changed the code to now take a datetime object to compare against rather than an amount of minutes. This perhaps makes the code easier to understand, but certainly more flexible.
function Copy-Unique {
# Copies files to a destination. If a file with the same name already exists in the destination,
# the function will create a unique filename by appending '(x)' after the name, but before the extension.
# The 'x' is a numeric sequence value.
[CmdletBinding(SupportsShouldProcess)] # add support for -WhatIf switch
Param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
[Alias("Path")]
[ValidateScript({Test-Path -Path $_ -PathType Container})]
[string]$SourceFolder,
[Parameter(Mandatory = $true, Position = 1)]
[string]$DestinationFolder,
[string]$Filter = '*',
[datetime]$NewerThan = [datetime]::MinValue,
[switch]$Recurse
)
# create the destination path if it does not exist
if (!(Test-Path -Path $DestinationFolder -PathType Container)) {
Write-Verbose "Creating folder '$DestinationFolder'"
$null = New-Item -Path $DestinationFolder -ItemType 'Directory' -Force
}
# get a list of file FullNames in this source folder
$sourceFiles = #(Get-ChildItem -Path $SourceFolder -Filter $Filter -File -Recurse:$Recurse)
# if you want only files newer than a certain date, apply an extra filter
if ($NewerThan -gt [datetime]::MinValue) {
$sourceFiles = #($sourceFiles | Where-Object { $_.LastWriteTime -gt $NewerThan })
}
foreach ($file in $sourceFiles) {
# get an array of all filenames (names only) of the files with a similar name already present in the destination folder
$destFiles = #((Get-ChildItem $DestinationFolder -File -Filter "$($file.BaseName)*$($file.Extension)").Name)
# for PowerShell version < 3.0 use this
# $destFiles = #(Get-ChildItem $DestinationFolder -Filter "$baseName*$extension" | Where-Object { !($_.PSIsContainer) } | Select-Object -ExpandProperty Name)
# construct the new filename
$newName = $file.Name
$count = 1
while ($destFiles -contains $newName) {
$newName = "{0}({1}){2}" -f $file.BaseName, $count++, $file.Extension
}
# use Join-Path to create a FullName for the file
$newFile = Join-Path -Path $DestinationFolder -ChildPath $newName
Write-Verbose "Copying '$($file.FullName)' as '$newFile'"
$file | Copy-Item -Destination $newFile -Force
}
}
# you can change the folder paths, file pattern to filter etc. here
$destFolder = Join-Path -Path 'D:\Destination' -ChildPath ('{0:yyyy-MM-dd_HH-mm}' -f (Get-Date))
Copy-Unique -SourceFolder "C:\Origin" -DestinationFolder $destFolder -Filter '*.dat' -Recurse -NewerThan (Get-Date).AddMinutes(-300)
When you have saved the above code to let's say 'C:\Scripts\Copy-Unique.ps1' you can then call it from a batch file like:
Powershell.exe -NoLogo -NoProfile -File "C:\Scripts\Copy-Unique.ps1"
Get-ChildItem -Filter *_* | Foreach-Object -Process {
$NewName = [Regex]::Match($_.Name,"^[^ _]*").Value +'.jpg'
$_ | Rename-Item -NewName $NewName
}
I have been using this code to rename files
For example, 569_SOM_TEST.jpg to 569
but if there is 569_TOB_TEST.jpg, It gives an error Rename-Item : Cannot create a file when that file already exists.
I want it to make it 569-1.
How can I accomplish this?
To prevent renaming clashes, you can put the below helper function at the top of your script:
function Rename-FileUnique {
# Renames a file. If a file with that name already exists,
# the function will create a unique filename by appending '(x)' after the
# name, but before the extension. The 'x' is a numeric value.
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
[string]$Path,
[Parameter(Mandatory = $true, Position = 1)]
[string]$NewName,
[switch]$PassThru
)
# Throw a bit nicer error than with [ValidateScript({Test-Path -Path $_ -PathType Leaf})]
if (!(Test-Path -Path $Path -PathType Leaf)){
Throw [System.IO.FileNotFoundException] "Rename-FileUnique: The file '$Path' could not be found."
}
# split the new filename into a basename and an extension variable
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($NewName)
$extension = [System.IO.Path]::GetExtension($NewName) # this includes the dot
$folder = Split-Path -Path $Path -Parent
# get an array of all filenames (name only) of the files with a similar name already present in the folder
$allFiles = #(Get-ChildItem $folder -Filter "$baseName*$extension" -File | Select-Object -ExpandProperty Name)
# for PowerShell version < 3.0 use this
# $allFiles = #(Get-ChildItem $folder -Filter "$baseName*$extension" | Where-Object { !($_.PSIsContainer) } | Select-Object -ExpandProperty Name)
# construct the new filename / strip the path from the file name
$NewName = $baseName + $extension # or use $NewName = Split-Path $NewName -Leaf
if ($allFiles.Count) {
$count = 1
while ($allFiles -contains $NewName) {
$NewName = "{0}-{1}{2}" -f $baseName, $count++, $extension
}
}
Write-Verbose "Renaming '$Path' to '$NewName'"
Rename-Item -Path $Path -NewName $NewName -Force -PassThru:$PassThru
}
and use it like:
Get-ChildItem -Filter '*_*.jpg' | Foreach-Object {
# create the proposed new filename
$newName = '{0}.jpg' -f ($_.Name -split '_')[0]
$_ | Rename-FileUnique -NewName $newName
}
This will ensure that any proposed new filename gets a not already used index number attached to its basename.
I'm trying to write script which will copy content of files with .txt extension to one. Script is working but -recurse is not. (It dosn't copy files which are in sub folders) and I don't know why is that. This is how my script looks like:
function UnifyConfigs {
param (
$destination = "C:\temp\all.txt",
[Parameter()]
$files
)
foreach ($config in $files) {
If((Get-ChildItem $config -Recurse).LastWriteTime -gt (Get-Item $destination).LastWriteTime)
{
Clear-Content -path $destination
Set-Content -path $destination -value (Get-Content $config)
}
else {
break
}
}
}
And yes: I have tried it with -force :-)
First up, you need to move the Get-ChildItem -Recurse call to where you resolve the input string to actual files in the filesystem:
foreach ($config in Get-ChildItem $files -Recurse) {
if($config.LastWriteTime -gt (Get-Item $destination).LastWriteTime)
{
Clear-Content -path $destination
Set-Content -path $destination -value (Get-Content $config)
}
else {
break
}
}
If you just want to test that any of the input files are newer than the destination file and then overwrite the contents of the destination with all of the other txt files, that actually becomes a tad simpler - we can discard the outer loop completely:
# Discover all the files
$configFiles = Get-ChildItem $files -Recurse
# use `-gt` and the destination timestamp to "filter" all the config file timestamps
# if _any_ of them are newer that $destination, then the condition is true
if(#($configFiles.LastWriteTime) -gt (Get-Item $destination).LastWriteTime){
# pipe every file to Get-Content, and then overwrite $destination with the whole thing
$configFiles |Get-Content |Set-Content -Path $destination -Force
}
I'd also recommend refactoring the parameter names to better reflect what the expected input is ("C:\path\to*files" is a string representing a "path", it is not "files"):
function Update-UnifiedConfig {
param (
[Parameter(Mandatory = $false)]
[string]$DestinationPath = "C:\temp\all.txt",
[Parameter(Mandatory = $true)]
[string]$Path
)
$destinationLastModified = (Get-Item -LiteralPath $DestinationPath).LastWriteTime
$configFiles = Get-ChildItem $files -Recurse
if(#($configFiles.LastWriteTime) -gt $destinationLastModified){
$configFiles |Get-Content |Set-Content -LiteralPath $DestinationPath -Force
}
}
The reason I'm using -LiteralPath in most places above is because $DestinationPath is just that, -Path on the other hand will treat wildcards as expandable which is only appropriate for the $Path parameter value in this function
I have a powershell script that takes all of the files in one directory, renames the first one and moves it, and then goes to the next file. Sometimes there will be multiple files that get renamed to the same name (because of the system that it's going to, not ideal and will have to change) and was overwriting files that shouldn't have been getting overwritten with -force. I need all of the files to move but also have unique names so we have them available in the destination location. Is there an easy way to have it automatically rename so it would look like:
123.txt
123(1).txt
123(2).txt
or
123.txt
123_1.txt
123_2.txt
There's no built-in way to do that. Give this a try:
$src = "d:\temp"
$dest = "d:\temp1"
$num=1
Get-ChildItem -Path $src -Filter *.txt -Recurse | ForEach-Object {
$nextName = Join-Path -Path $dest -ChildPath $_.name
while(Test-Path -Path $nextName)
{
$nextName = Join-Path $dest ($_.BaseName + "_$num" + $_.Extension)
$num+=1
}
$_ | Move-Item -Destination $nextName
}
#Fixed solution basing on previous answer (variable $num moved into for each loop):
$src = "d:\temp"
$dest = "d:\temp1"
Get-ChildItem -Path $src -Filter *.txt -Recurse | ForEach-Object {
$num=1
$nextName = Join-Path -Path $dest -ChildPath $_.name
while(Test-Path -Path $nextName)
{
$nextName = Join-Path $dest ($_.BaseName + "_$num" + $_.Extension)
$num+=1
}
$_ | Move-Item -Destination $nextName
}
So I'm way late to the party here but... I kinda liked the responses just didn't like the lack of it being in a function so... I modified it for re-usability.
I added the Name field because I'm using this as part of another process where I am using a regex substring process to take a file name which contains <owner>-<date_string>-<hash>.<extension> and moving them into a <file_path>/<owner>/<date_string>.<extension> format.
function Move-Item-AutoRename {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline)]
[String]$Source,
[Parameter(Mandatory = $true)]
[String]$Destination,
[Parameter(Mandatory = $true)]
[String]$Name
)
PROCESS {
$count = 1
[System.IO.FileInfo]$nextName = Join-Path -Path $Destination -ChildPath $Name
while (Test-Path -Path $nextName) {
$nextName = Join-Path -Path $Destination ($Name.Split(".")[0] + "_$($count)" + $nextName.Extension)
$count += 1
}
Move-Item -Path $Source -Destination $nextName
}
}
Since I am sure others may be interested in the other part of this solution I will include it just for sharing sake.
function Import-UnsortedFiles {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline)]
[String]$Source,
[Parameter(Mandatory = $true)]
[String]$Destination
)
PROCESS {
Get-ChildItem -Path $Source -Include *.xml, *.json | ForEach-Object {
$results = $_.Name | Select-String -Pattern '^(.*)-([0-9]*_[0-9]*)-.*(\..*)$'
[System.IO.FileInfo] $dest = (Join-Path -Path $Target -ChildPath "$($results.Matches.Groups[1])/$($results.Matches.Groups[2])$($results.Matches.Groups[3])");
$test = (Test-Path -Path $dest.Directory.FullName -PathType Container)
if ($test -eq $false) {
New-Item -Path $dest.Directory.FullName -ItemType Directory;
}
Move-Item-AutoRename -Source $_.FullName -Destination $dest.Directory.FullName -Name $dest.Name
}
}
}
Call this by invoking Import-UnsortedFiles -Source $Source -Destination $Target
I have a powershell script that takes all of the files in one directory, renames the first one and moves it, and then goes to the next file. Sometimes there will be multiple files that get renamed to the same name (because of the system that it's going to, not ideal and will have to change) and was overwriting files that shouldn't have been getting overwritten with -force. I need all of the files to move but also have unique names so we have them available in the destination location. Is there an easy way to have it automatically rename so it would look like:
123.txt
123(1).txt
123(2).txt
or
123.txt
123_1.txt
123_2.txt
There's no built-in way to do that. Give this a try:
$src = "d:\temp"
$dest = "d:\temp1"
$num=1
Get-ChildItem -Path $src -Filter *.txt -Recurse | ForEach-Object {
$nextName = Join-Path -Path $dest -ChildPath $_.name
while(Test-Path -Path $nextName)
{
$nextName = Join-Path $dest ($_.BaseName + "_$num" + $_.Extension)
$num+=1
}
$_ | Move-Item -Destination $nextName
}
#Fixed solution basing on previous answer (variable $num moved into for each loop):
$src = "d:\temp"
$dest = "d:\temp1"
Get-ChildItem -Path $src -Filter *.txt -Recurse | ForEach-Object {
$num=1
$nextName = Join-Path -Path $dest -ChildPath $_.name
while(Test-Path -Path $nextName)
{
$nextName = Join-Path $dest ($_.BaseName + "_$num" + $_.Extension)
$num+=1
}
$_ | Move-Item -Destination $nextName
}
So I'm way late to the party here but... I kinda liked the responses just didn't like the lack of it being in a function so... I modified it for re-usability.
I added the Name field because I'm using this as part of another process where I am using a regex substring process to take a file name which contains <owner>-<date_string>-<hash>.<extension> and moving them into a <file_path>/<owner>/<date_string>.<extension> format.
function Move-Item-AutoRename {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline)]
[String]$Source,
[Parameter(Mandatory = $true)]
[String]$Destination,
[Parameter(Mandatory = $true)]
[String]$Name
)
PROCESS {
$count = 1
[System.IO.FileInfo]$nextName = Join-Path -Path $Destination -ChildPath $Name
while (Test-Path -Path $nextName) {
$nextName = Join-Path -Path $Destination ($Name.Split(".")[0] + "_$($count)" + $nextName.Extension)
$count += 1
}
Move-Item -Path $Source -Destination $nextName
}
}
Since I am sure others may be interested in the other part of this solution I will include it just for sharing sake.
function Import-UnsortedFiles {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline)]
[String]$Source,
[Parameter(Mandatory = $true)]
[String]$Destination
)
PROCESS {
Get-ChildItem -Path $Source -Include *.xml, *.json | ForEach-Object {
$results = $_.Name | Select-String -Pattern '^(.*)-([0-9]*_[0-9]*)-.*(\..*)$'
[System.IO.FileInfo] $dest = (Join-Path -Path $Target -ChildPath "$($results.Matches.Groups[1])/$($results.Matches.Groups[2])$($results.Matches.Groups[3])");
$test = (Test-Path -Path $dest.Directory.FullName -PathType Container)
if ($test -eq $false) {
New-Item -Path $dest.Directory.FullName -ItemType Directory;
}
Move-Item-AutoRename -Source $_.FullName -Destination $dest.Directory.FullName -Name $dest.Name
}
}
}
Call this by invoking Import-UnsortedFiles -Source $Source -Destination $Target