Copy-Item and Join-Path creates a folder instead of a file - powershell

I have an issue where combining Copy-Item and using Join-Path in the destination leads to folder creation with file & extension name in folder name instead of copying the original file.
I have tried several variations of the following code. Also I have tried using {} for Destination to make it a script block but that ended up throwing an error saying that there was no input and therefore it could not be evaluated. I have also tried setting -ItemType to file, but that throws the same exception error. My PS version is v2.0.1.1 if that helps.
Assume all variables are instantiated and work properly, I have tested each one individually
Get-ChildItem -Path $Src -Recurse -Force | Where-Object {$_.Name -notcontains "Archive"}|
ForEach-Object {
if(!$_.PSIsContainer){
$DateStr = $_.BaseName.Substring(0,2)+'/'+$_.BaseName.Substring(3,2)+'/'+$_.BaseName.Substring(6,4)
$FileDate = get-date $DateStr
If ( $FileDate -ge $Date ) {
$ParentOjbect = $_.Directory
$Parent = $ParentOjbect.Name
Copy-Item -Path (Join-Path -Path $Src -ChildPath '\*' ) -Destination (Join-Path $Dst '{0} {1}' -f $Parent,$_.Name)
}}
}

Related

PowerShell script to copy jpg files from one folder to another by creating two subfolders with the same name

I am in need of some assistance, I am new to PowerShell and am trying to use it to make some of my work easier. I am writing a PowerShell script to copy JPG files from one location (C:\Pictures\People\People) and moving them to a new location.
The issue is that in this new location I need to create a folder with the same name as the JPG and then another subfolder with the same name again as the JPG.
So I need to move images from C:\Pictures\People\People which I will call JPG_Image to C:\Pictures\JPG_Name\JPG_Name\'JPG_Image'
So far I found and have been working with this:
$SourceFolder = "C:\Pictures\People\People"
$TargetFolder = "C:\Pictures\"
# Find all files matching *.JPG in the folder specified
Get-ChildItem -Path $SourceFolder -Filter *.jpg |
ForEach-Object {
$ChildPath = Join-Path -Path $_.Name.Replace('.jpg','') -ChildPath $_.Name
[System.IO.FileInfo]$Destination = Join-Path -Path $TargetFolder -ChildPath $ChildPath
# Create the directory if it doesn't already exits
if( -not ( Test-Path -Path $Destination.Directory.FullName ) ){
New-Item -ItemType Directory -Path $Destination.Directory.FullName
}
Copy-Item -Path $_.FullName -Destination $Destination.FullName
}
You are making this harder on yourself than needs be.
Some enhancements to your code:
Add switch -File to the Get-ChildItem cmd so you do not also get DirectoryInfo objects
To get the filename without extension, there is a property .BaseName
Join-Path returns a string, no need to cast that into a [System.IO.FileInfo] object
If you add -Force to the New-Item cmd, there is no need to check if a folder already exists, because that will make the cmdlet either create a new folder or return the existing DirectoryInfo object.
Because we don't need that object (and the console output from it), we can just throw that away using $null = New-Item ...
Putting it all together:
$SourceFolder = "C:\Pictures\People\People"
$TargetFolder = "C:\Pictures"
# Find all files matching *.JPG in the folder specified
Get-ChildItem -Path $SourceFolder -Filter '*.jpg' -File |
ForEach-Object {
# Join-Path simply returns a string containing the combined path
# The BaseName property is the filename without extension
$ChildPath = Join-Path -Path $_.BaseName -ChildPath $_.BaseName
$Destination = Join-Path -Path $TargetFolder -ChildPath $ChildPath
# Create the directory if it doesn't already exits
# Using -Force will not give an error if the folder already exists
$null = New-Item -Path $Destination -ItemType Directory -Force
$_ | Copy-Item -Destination $Destination
}

PowerShell - sort&move files by Edit date

Simple task. But can't realize.
I just need to take file from one directory (x) and move to another (z) + create there folder named "YYYY" (year of last edit), then, subfolder named "MM" (month of last edit).
So i find script and tried to adapt it for my needs. But, i can't run it succsesfully.
Here is code witch i took as example:
$files= Get-ChildItem -File «C:\Files\»
foreach ($file in $files) {
if ($file.lastwritetime -lt $lastweek) {
$file | Move-Item -Force -Destination { md («C:\Files\» + $_.LastWriteTime.ToString(«yyyy.MM») + «\» + $_.LastWriteTime.ToString(«yyyy.MM.dd»)) -Force}
}
}
So i made mine based on it:
$files= Get-childitem -path "c:\Files"
$files | foreach-object {Move-Item -Force -Destination { md ("C:\Files\" + $_.LastWriteTime.ToString("yyyy")+"\"+$_.LastWriteTime.ToString("MM"))}}
Please help me make it work!
Im not really good with powershell...
Better not construct paths by concatenating strings with +. To avoid errors, use the Join-Path cmdlet for that.
Also I would advice not trying to cram that much in one line of code, because when it fails, it is really hard to debug.
Try
$path = 'C:\Files'
Get-childitem -Path $path -File |
ForEach-Object {
$destination = Join-Path -Path $path -ChildPath ('{0}\{1:D2}' -f $_.LastWriteTime.Year, $_.LastWriteTime.Month)
$null = New-Item -Path $destination -ItemType Directory -Force
$_ | Move-Item -Destination $destination
}
You can shorten the code by creating tyhe destination path like this: $destination = Join-Path -Path $path -ChildPath ('{0:yyyy\\MM}' -f $_.LastWriteTime), but then you have to remember to escape the backslash in the -f Format template string by doubling it

Powershell move files and folders based on older than x days

I am new to powershell and trying to learn a basic file move from one directory to another. My goal is to move files and folders that are over 18months old to cold storage folder run as a scheduled Task. I need to be able to easily modify it's directories to fit our needs. It needs to preserve the folder structure and only move files that fit the above parameters. I also need it to log everything it did so if something is off i know where.
If I run this it just copies everything. If I comment out the %{Copy-Item... then it runs and lists only based on my parameters and logs it. Where am I going wrong or am I way off base?
Yes it would be easy to use robocopy to do this but I want to use powershell and learn from it.
#Remove-Variable * -ErrorAction SilentlyContinue; Remove-Module *; $error.Clear();
#Clear-Host
#Days older than
$Days = "-485"
#Path Variables
$Sourcepath = "C:\Temp1"
$DestinationPath = "C:\Temp2"
#Logging
$Logfile = "c:\temp3\file_$((Get-Date).ToString('MM-dd-yyyy_hh-mm-ss')).log"
#transcript logs all outputs to txt file
Start-Transcript -Path $Logfile -Append
Get-ChildItem $Sourcepath -Force -Recurse |
Where-Object {$_.LastwriteTime -le (Get-Date).AddDays($Days)} |
% {Copy-Item -Path $Sourcepath -Destination $DestinationPath -Recurse -Force}
Stop-Transcript
Problem
Copy-Item -Path $Sourcepath -Destination $DestinationPath -Recurse -Force
You always specify the same path for source and destination. With parameter -recurse you will copy the whole directory $SourcePath for each matching file.
Solution
You need to feed the output of the previous pipeline steps to Copy-Item by using the $_ (aka $PSItem) variable, basically using Copy-Item in single-item mode.
Try this (requires .NET >= 5.0 for GetRelativePath method):
Get-ChildItem $Sourcepath -File -Force -Recurse |
Where-Object {$_.LastwriteTime -le (Get-Date).AddDays($Days)} |
ForEach-Object {
$relativeSourceFilePath = [IO.Path]::GetRelativePath( $sourcePath, $_.Fullname )
$destinationFilePath = Join-Path $destinationPath $relativeSourceFilePath
$destinationSubDirPath = Split-Path $destinationFilePath -Parent
# Need to create sub directory when using Copy-Item in single-item mode
$null = New-Item $destinationSubDirPath -ItemType Directory -Force
# Copy one file
Copy-Item -Path $_ -Destination $destinationFilePath -Force
}
Alternative implementation without GetRelativePath (for .NET < 5.0):
Push-Location $Sourcepath # Base path to use for Get-ChildItem and Resolve-Path
try {
Get-ChildItem . -File -Force -Recurse |
Where-Object {$_.LastwriteTime -le (Get-Date).AddDays($Days)} |
ForEach-Object {
$relativeSourceFilePath = Resolve-Path $_.Fullname -Relative
$destinationFilePath = Join-Path $destinationPath $relativeSourceFilePath
$destinationSubDirPath = Split-Path $destinationFilePath -Parent
# Need to create sub directory when using Copy-Item in single-item mode
$null = New-Item $destinationSubDirPath -ItemType Directory -Force
# Copy one file
Copy-Item -Path $_ -Destination $destinationFilePath -Force
}
}
finally {
Pop-Location # restore previous location
}
On a side note, $Days = "-485" should be replaced by $Days = -485.
You currently create a string instead of a number and rely on Powershell's ability to automagically convert string to number when "necessary". This doesn't always work though, so better create a variable with the appropriate datatype in the first place.

Rename After Copy - Powershell

I am trying to use powershell to copy one type of file (.xlsx) from one folder to another.
Once the copy is completed, I would like the extension on the original file to be changed. (.xlsx to .cmp)
I have the copy part down (below) but I am lost when it comes to the rename. Can you guys please help. I am a PS noob! Thank you.
$src = "C:\Users\x\Documents\Test1"
$dst = "C:\Users\x\Documents\Test2"
Get-ChildItem $src -Filter "*.xlsx" | Move-Item -Destination $dst -Force
As far as I know, you'll have to iterate over your files to be able to perform this rename.
# Set-up variables
$sourcePath = "C:\temp"
$sourceExtension = "txt"
$destinationPath = "C:\temp2"
$destinationExtension = "cmp"
# Grab the list of files
$files = Get-ChildItem -Path $sourcePath -Filter "*.$sourceExtension"
# Loop over the files
foreach ($file in $files) {
# Construct the new file name
$newFileName = (Join-Path -Path $destinationPath -ChildPath $file.BaseName) + ".$destinationExtension"
Write-Output "New File Name = $newFileName"
# Move the file to the new destination with its new name!
Move-Item -Path $file.FullName -Destination $newFileName
}
Note: BaseName = filename without extension
This should do it:
$src = "C:\Users\x\Documents\Test1"
$dst = "C:\Users\x\Documents\Test2"
Get-ChildItem $src -Filter "*.xlsx" | ForEach-Object {
Copy-Item $_ -Destination $dst
Rename-Item $_ -NewName ($_.Name -Replace '.xlsx','.cmp')
}
Uses a ForEach-Object loop to go through each item in the $src folder. Then for each item (represented inside the loop as $_) we use Copy-Item to copy it to the destination Then use Rename-Item with a -Replace to change the file extension.

How to find the path of the current object in a for-eachobject loop

I am trying to find the path of the object currently being operated on in a for-eachobject loop. Can someone explain how to do this? I am trying to set the path of the source for Copy-Item to whatever path the file is on. This is the first project I have worked with Powershell on. Assume $Src has been properly instantiated.
Get-ChildItem -Path $Src -Recurse -Force | Where-Object {$_.Name -notmatch "Archive" -and ($_.PSIsContainer)}|
ForEach-Object {
$DateStr = $_.BaseName.Substring(0,2)+'/'+$_.BaseName.Substring(3,2)+'/'+$_.BaseName.Substring(6,4)
$FileDate = get-date $DateStr
If ( $FileDate -ge $Date -and !($_.PSIsContainer)) {
$ParentOjbect = $_.Directory
$Parent = $ParentOjbect.Name
Copy-Item -Path (Join-Path -Path $Src -ChildPath '\*.txt' ) #this
#needs to be corrected to only point to the current file
-Dest (Join-Path $Dst ("$($_.Directory.Name) $($_.Name)")) -WhatIf
}
}
Copy-Item -Path (Join-Path -Path $_.FullName -ChildPath '\*.txt' )
I suggest you also use -WhatIf on that also to check it does what you expect first.
$_, or $PSItem (PowerShell 3.0+), accesses the current object in the pipeline. From here, you can access its members.