How can i only copy folders with robocopy in powershell? - powershell

How can i copy only the folders in a directory with powershell and robocopy?
Get-ChildItem 'C:\temp\test' |
ForEach-Object {
$newname = ($_.BaseName -Replace '[^\x20-\x5D,\x60-\x7E]+', '-')
write-host $_.BaseName
write-host $newname
robocopy.exe 'C:\temp\test\'$_.BaseName 'C:\temp\test\copy\'$newname
}
Edit
Thanks works great
Get-ChildItem 'C:\temp\test' |
ForEach-Object {
$newname = ($_.BaseName -Replace '[^\x20-\x5D,\x60-\x7E]+', '-')
if (($_.GetType()).Name -eq "DirectoryInfo"){
write-host "folder"
}
write-host $_.BaseName
write-host $newname
robocopy.exe "C:\temp\test\$($_.BaseName)" "C:\temp\test\copy\$newname"
}

Corrected errors, modified code below.
Get-ChildItem 'C:\temp\test' |
ForEach-Object {
$newname = ($_.BaseName -Replace '[^\x20-\x5D,\x60-\x7E]+', '-')
write-host $_.BaseName
write-host $newname
robocopy.exe "C:\temp\test\$($_.BaseName)" "C:\temp\test\copy\$newname"
}

You could do something like:
$items = Get-ChildItem 'C:\temp\test'
foreach($item in $items){
if(($item.GetType()).Name -eq "DirectoryInfo"){
$newname = ($item.BaseName -Replace '[^\x20-\x5D,\x60-\x7E]+', '-')
Write-Host $item.BaseName
Write-Host $newname
robocopy.exe "C:\temp\test\$($_.BaseName)" "C:\temp\test\copy\$newname"
}else{
Write-Host "$item is not a directory"
}
}

Related

Powershell: Find folders with a specific name, and move contents up one level, rename if exists

I've got the first two-thirds of this one accomplished, but I'm stuck on the last part. I've got a script that searches for subfolders with a specific name, and moves their contents up one level. I have another script that moves files from one place to another, and renames them if the file already exists. What I'm trying to do now is merge the two. So here's the one that moves files up:
$sourceDir="E:\Deep Storage"
$searchFolder="Draft Materials"
Get-ChildItem -path $sourceDir -filter $searchFolder -Recurse |
ForEach-Object {
Get-ChildItem -File -Path $_.FullName |
ForEach-Object {
Move-Item -Path $_.FullName -Destination $(Split-Path -Parent $_.PSParentPath)
}
}
And here's the one that moves things while renaming if they already exist:
$sourceDir="E:\Test1"
$targetDir="E:\Deep Storage\Test1"
Get-ChildItem -Path $sourceDir -Filter *.* -Recurse | ForEach-Object {
$num=1
$nextName = Join-Path -Path $targetDir -ChildPath $_.name
while(Test-Path -Path $nextName)
{
$nextName = Join-Path $targetDir ($_.BaseName + "_$num" + $_.Extension)
$num+=1
}
$_ | Copy-Item -Destination $nextName -Verbose
}
And lastly, my attempt to hybridize the two:
$sourceDir="E:\Deep Storage"
$searchFolder="Draft Materials"
Get-ChildItem -path $sourceDir -filter $searchFolder -Recurse |
ForEach-Object {
Get-ChildItem -File -Path $_.FullName |
ForEach-Object {
$num=1
$nextName = Join-Path -Path $_.FullName -Destination $(Split-Path -Parent $_.PSParentPath)
while(Test-Path -Path $nextName)
{
$nextName = Join-Path -Path $_.FullName -Destination $(Split-Path -Parent $_.PSParentPath) ($_.BaseName + "_$num" + $_.Extension)
$num+=1
}
$_ | Copy-Item -Destination $nextName
}
}
I feel like I'm on the right track, but after two hours of attempts I haven't been able to get this to work.
EDIT: providing the exact syntax I'm giving it
$sourceDir="E:\Deep Storage\Projects"
$searchFolder="Draft Materials"
$destinationPath = "$($sourceDir)\.."
Write-Host "OPERATION: Search for Folders Named" -ForegroundColor White -BackgroundColor DarkGreen -NoNewLine;
Write-Host " '$searchFolder' " -ForegroundColor Yellow -BackgroundColor DarkGreen -NoNewLine;
Write-Host "and Move Contents Up One Level" -ForegroundColor White -BackgroundColor DarkGreen;
Write-Host "SEARCHDIR: $sourceDir" -ForegroundColor White -BackgroundColor DarkGreen;
# Get all directories in specific folders inside the source directory
$folders = Get-ChildItem -Path $sourceDir -Directory | Where-Object {$_.Name -like "$searchFolder*" -or $_.FullName -like "*\$searchFolder\*"}
foreach ($folder in $folders) {
# Get all files in the current folder
$files = Get-ChildItem -Path $folder.FullName
foreach ($file in $files) {
$destinationFile = Join-Path -Path $destinationPath -ChildPath $file.Name
if (Test-Path $destinationFile) {
# If a file with the same name exists in the destination directory, rename it
$name = $file.Name
$extension = $file.Extension
$i = 0
while (Test-Path $destinationFile) {
$i++
$name = "{0}_{1}{2}" -f ($file.BaseName, $i, $extension)
$destinationFile = Join-Path -Path $destinationPath -ChildPath $name
}
Write-Host "Renaming $($file.Name) to $name"
}
Move-Item $file.FullName $destinationFile -Verbose -WhatIf
}
}
Here's what I came up with reading OP's post, code, and comments:
$sourcePath = 'E:\Deep Storage\Projects'
$searchFolder = 'Draft Materials'
Write-Host "OPERATION: Search for Folders Named" -ForegroundColor White -BackgroundColor DarkGreen -NoNewLine;
Write-Host " '$searchFolder' " -ForegroundColor Yellow -BackgroundColor DarkGreen -NoNewLine;
Write-Host "and Move Contents Up One Level" -ForegroundColor White -BackgroundColor DarkGreen;
Write-Host "SEARCHDIR: $sourcePath" -ForegroundColor White -BackgroundColor DarkGreen;
# Get all directories in specific folders inside the source directory
$folders = (Get-ChildItem -LiteralPath $sourcePath -Directory -Recurse) | Where-Object Name -match $searchFolder
### Check selected folders
$Folders.FullName | Out-GridView -Title 'Selected Folders'
Read-Host 'Paused. Check selected folders in GridView. Press <enter> to continue '
###
ForEach ($folder in $folders)
{
# Get all files in the current folder
$filesToCopy = $folder | Get-ChildItem -File
# Get list of names for exising files in target (parent folder)
$targetPath = $folder.Parent.FullName
$filesInTarget = (Get-ChildItem -LiteralPath $targetPath -File).Name
ForEach ($file in $filesToCopy)
{
If ($file.Name -notIn $filesInTarget)
{
$file | Move-Item -Destination $targetPath -Verbose -WhatIf
}
Else
{
$i = 0
Do
{
$newName = '{0}_{1}{2}' -f ($file.BaseName, $i++, $file.Extension)
} Until ( $newName -notIn $FilesInTarget )
Write-Host ('Renaming "{0}" to "{1}"...' -f $file.Name , $newName)
$file | Move-Item -Destination (Join-Path $targetPath $newName) -Verbose -WhatIf
}
}
# Delete (hopefully empty) folder
If (!($folder | Get-ChildItem -Force))
{
$folder | Remove-Item -WhatIf
}
Else
{
Write-Host ('Items still exist in "{0}". Folder not deleted.' -f $folder.FullName)
}
}
Syntax choice: For any cmdlet that has Path/LiteralPath parameter sets (gci, Copy, Move, Rename, etc.), the System.IO.FileSystemInfo | <Cmdlet> syntax succeeds with items that would fail in the <Cmdlet> -Path (System.IO.FileSystemInfo).FullNaame form becasue special characters in their name would require the -LiteralPath parameter.
In many cases replacing -Path with -LiteralPath (or its alias: -lp) will work as well. But the pipelined format reads "cleaner" (IMHO) when scanning code and, if you're just learning PowerShell, reminds you to think in terms of pipelining whenever possible and avoiding intermediate variables. Just for grins, here's a version of the above code where items are piped as much as possible, using ForEach-Object:
(Get-ChildItem -LiteralPath $sourcePath -Directory -Recurse) |
where Name -match $searchFolder |
ForEach-Object {
# Get list of names for exising files in target (parent folder)
$targetPath = $_.Parent.FullName
$filesInTarget = (Get-ChildItem -LiteralPath $targetPath -File).Name
$_ | Get-ChildItem -File | ForEach-Object {
If ($_.Name -notIn $filesInTarget)
{
$_ | Move-Item -Destination $targetPath -Verbose -WhatIf
}
Else
{
$i = 0
Do
{
$newName = '{0}_{1}{2}' -f ($_.BaseName, $i++, $_.Extension)
} Until ( $newName -notIn $FilesInTarget )
Write-Host ('Renaming "{0}" to "{1}"...' -f $_.Name , $newName)
$_ | Move-Item -Destination (Join-Path $targetPath $newName) -Verbose -WhatIf
}
}
# Delete (hopefully empty) folder
If (!($_ | Get-ChildItem -Force))
{
$_ | Remove-Item -WhatIf
}
Else
{
Write-Host ('Items still exist in "{0}". Folder not deleted.' -f $_.FullName)
}
}
please try this:
$sourcePath = "C:\temp\test\Test"
$destinationPath = "$($sourcePath)\.."
# Get all files in the source directory
$files = Get-ChildItem -Path $sourcePath
foreach ($file in $files) {
$destinationFile = Join-Path -Path $destinationPath -ChildPath $file.Name
if (Test-Path $destinationFile) {
# If a file with the same name exists in the destination directory, rename it
$name = $file.Name
$extension = $file.Extension
$i = 0
while (Test-Path $destinationFile) {
$i++
$name = "{0}_{1}{2}" -f ($file.BaseName, $i, $extension)
$destinationFile = Join-Path -Path $destinationPath -ChildPath $name
}
Write-Host "Renaming $($file.Name) to $name"
}
Move-Item $file.FullName $destinationFile
}
This will not delete the original location and will move everything from the sourcePath to the parent location.
To delete the original location just add at the end:
Remove-Item -Path $sourcePath -Force -Confirm:$false
UPDATE1:
$sourcePath = "C:\temp\test\Test"
$destinationPath = "$($sourcePath)\.."
# Get all directories in specific folders inside the source directory
$folders = Get-ChildItem -Path $sourcePath -Directory | Where-Object {$_.Name -like "Folder1*" -or $_.FullName -like "*\Folder2\*"}
foreach ($folder in $folders) {
# Get all files in the current folder
$files = Get-ChildItem -Path $folder.FullName
foreach ($file in $files) {
$destinationFile = "$($folder.FullName)\.."
if (Test-Path $destinationFile) {
# If a file with the same name exists in the destination directory, rename it
$name = $file.Name
$extension = $file.Extension
$i = 0
while (Test-Path $destinationFile) {
$i++
$name = "{0}_{1}{2}" -f ($file.BaseName, $i, $extension)
$destinationFile = Join-Path -Path $destinationPath -ChildPath $name
}
Write-Host "Renaming $($file.Name) to $name"
}
Move-Item $file.FullName $destinationFile
}
}

Recover multiple original files powershell due to onedrive sync issues

We had a syncing issue that affected thousands of files.
A backup of the correct files was created in each folder by the sync tool. the backup has " -LocalPCName" added before the dot before the extension.
I am really a beginner in powershell.
I am trying to write a powershell script to go trough every folder and check if a file backup was create d for each file.
If a backup was created, I want to rename the file to add " -Wrongversion" and remove the " -LocalPCName" from the correct file.
This will basically recover the file to the correct version and make a copy of the wrong version.
I wrote this code but i am blocked at trying to match using -replace
Get-ChildItem *.* |
ForEach-Object {
If ($_.name -match "\s-LocalPCName")
{Get-ChildItem $_.Name -replace "\s-LocalPCName",""
}
}
Thank you for your help!
here is a newer version of my code but i get errors and i am concerned of using recurse, am i missing anything when this will go to subfolders? In this code, the LocalPCName is Didi2015
Get-ChildItem *.* -recurse|
ForEach-Object {
If ($_.name -match "\s-Didi2015")
{
$OldName = $_.Name -replace "\s-Didi2015",""
$dir=$_.DirectoryName
$ext = $_.Extension
$file = $OldName.Substring(0, $OldName.LastIndexOf('.'))
Write-Output $dir
Write-Output $ext
Write-Output $file
$Newname = $file + " -Wrongversion" + $ext
Write-Output $Newname
Rename-Item -Path $OldName -NewName $Newname -WhatIf
Rename-Item -Path $_ -NewName $OldName -WhatIf
}
}
Here is my final code. it works.
dir *-Didi2015*.* -Recurse|
ForEach-Object {
If ($_.name -match "-Didi2015")
{
$OldName = $_ -replace "-Didi2015",""
$dir=$_.DirectoryName
$ext = $_.Extension
$file = $OldName.Substring(0, $OldName.LastIndexOf('.'))
# Write-Output $dir
# Write-Output $ext
# Write-Output $file
# Write-Output $OldName
# Write-Output $Newname
# Write-Output $_.Name
$Newname = $file + "-Wrongversion" + $ext
Rename-Item -Path $OldName -NewName $Newname -WhatIf
Rename-Item -Path $OldName -NewName $Newname -Force
Rename-Item -Path $_ -NewName $OldName -WhatIf
Rename-Item -Path $_ -NewName $OldName -Force
}
}

Output Rename-Item events to log file

I'm trying to log data surrounding the renaming of files using the following script. The only problem is that the log file contains files that were not renamed due to 'access denied' errors when attempting to rename. I need to figure out how to only create log entries for files that were SUCCESSFULLY renamed or pipe the failed renames to a different log file. I'd also like the total number of files renamed listed at the top of the log file if at all possible(ie 'xxx files were renamed') I appreciate any suggestions for getting this to work using powershell v2.
$drivesArray = Get-PSDrive -PSProvider 'FileSystem' | select -Expand Root
foreach ($drive in $drivesArray) {
Get-ChildItem $drive | Where-Object {
$_.FullName -notlike "${Env:WinDir}*" -and
$_.FullName -notlike "${Env:ProgramFiles}*"
} | ForEach-Object {
Get-ChildItem $_.FullName -Recurse -ErrorAction SilentlyContinue
} | Where-Object {
-not $_.PSIsContainer -and
$_.Extension -notmatch '^\.(xxx|exe|html)$'
} | ForEach-Object {
$newName = $_.FullName + '.xxx';
Rename-Item -Path $_.FullName -NewName ($_.FullName + '.xxx') -ErrorAction SilentlyContinue
Add-Content c:\temp\renameLog.txt -Value $('{0} {1} {2} {3}' -f $(Get-Date),$_.fullname,$_.name,$newName )
}
}
Here is an example, untested of course.
$success = 0
$failed = 0
$drivesArray = Get-PSDrive -PSProvider 'FileSystem' | select -Expand Root
foreach ($drive in $drivesArray) {
Get-ChildItem $drive | Where-Object {
$_.FullName -notlike "${Env:WinDir}*" -and
$_.FullName -notlike "${Env:ProgramFiles}*"
} | ForEach-Object {
Get-ChildItem $_.FullName -Recurse -ErrorAction SilentlyContinue
} | Where-Object {
-not $_.PSIsContainer -and $_.Extension -notmatch '^\.(xxx|exe|html)$' -and $_.Name -notmatch '^renameLog.txt|^renameLog_failed.txt'
} | ForEach-Object {
try {
$newName = $_.FullName + '.xxx';
Rename-Item -Path $_.FullName -NewName ($_.FullName + '.xxx') -ErrorAction Stop
Add-Content c:\temp\renameLog.txt -Value $('{0} {1} {2} {3}' -f $(Get-Date),$_.fullname,$_.name,$newName )
$success ++
} catch [exception] {
Add-Content c:\temp\renameLog_failed.txt -Value $('{0} {1} {2} {3}' -f $(Get-Date),$_.fullname,$_.name,$newName )
$failed ++
$error.Clear()
}
}
}
Add-Content "c:\temp\renameLog.txt" -Value $("Total: " + $success)
Add-Content "c:\temp\renameLog_failed.txt" -Value $("Total: " + $failed)

Logging actions of my powershell into a file

I'm actually doing a script to replace every "o" into my folder/files names into "0"
Here is my simple script :
Get-ChildItem -recurse |`
Where-Object { $_.name -match 'o' } |`
Rename-Item -NewName { $_name -replace 'o', '0' }
It is working but now I want to log into a file every files/folder that have changed.
So basicly I want to add a function to write the name of my file before and after being renamed.
For example, for the "BEFORE name", I tried to add this command after the Where-Object pipe :
Add-content $MylogFile
It succed to log every files having a 'o' inside but WITHOUT their path. And having a log of 1000+ files without their path is useless...
DO you have any idea to simply log the before/after name of each file that have been renamed by the script WITH their path ?
Thanks in advance !
Regards,
You just need to store the original search into a variable then iterate over it. The following should do what you want:
If you want to rename only files:
$files = Get-ChildItem -recurse | Where-Object { $_.PSIscontainer -eq $false -and $_.name -match 'o' }
$files | % {
$newname = $_.name -replace 'o', '0'
write-host "Before: $($_.fullname)"
write-host "After: $newname"
$_ | Rename-Item -NewName $newname
}
To rename files AND folders:
$files = Get-ChildItem -recurse | Where-Object { $_.PSIscontainer -eq $false -and $_.name -match 'o' }
$files | % {
$newname = $_.name -replace 'o', '0'
write-host "Before: $($_.fullname)"
write-host "After: $newname"
$_ | Rename-Item -NewName $newname
}
$dirs = Get-ChildItem -recurse | Where-Object { $_.PSIscontainer -eq $true -and $_.name -match 'o' }
$dirs | % {
$newname = $_.name -replace 'o', '0'
write-host "Dir Before: $($_.fullname)"
write-host "Dir After: $newname"
$_ | Rename-Item -NewName $newname
}

Replace multiple strings in a file using powershell

I am using following coe to replace the string
$folders=Get-ChildItem -Path "C:\temp\Database Scripts"
foreach($folder in $folders)
{
Write-Host $folder
$spath=[string]::Concat("C:\temp\Database Scripts\", $folder)
$subfolders=Get-ChildItem $spath
foreach($subfolder in $subfolders )
{
if($subfolder -match "Running Scripts")
{
$subfolerpath=[string]::Concat($spath,"\",$subfolder,"\*")
$files =get-childitem -Path $subfolerpath -include "AVEVAScripts*"
if($files -ne $null)
{
foreach( $file in $files)
{
Write-Host $file;
(Get-Content $file) | ForEach-Object {$_ -replace "DATABASE_USER","fhghjgj" `
-replace "DATABASE_PASSWORD", "DFGHFHJGJH" } |Set-Content $file
}
}
}
}
}
But ending up with following error.
Set-Content : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
Please help :)
Remove the $x in the end of Set-Content. $x is never declared.
Also, you could simplify it a lot. Ex:
Get-ChildItem -Filter "Running Scripts" -Path "C:\temp\Database Scripts" -Recurse | ForEach-Object {
Get-ChildItem -Path $_.FullName -Filter "AVEVAScripts*" -Recurse | ForEach-Object {
(Get-Content $_.FullName) | ForEach-Object {
$_ -replace "DATABASE_USER","fhghjgj" -replace "DATABASE_PASSWORD", "DFGHFHJGJH"
} | Set-Content $_.FullName
}
}
Or find all files that includes "AVEVAScripts" in it's name, then check if their full path includes "Running Scripts"
Get-ChildItem -Filter "AVEVAScripts*" -Path "C:\temp\Database Scripts" -Recurse |
Where-Object { $_.FullName -like "*Running Scripts*" } |
ForEach-Object {
(Get-Content $_.FullName) | ForEach-Object {
$_ -replace "DATABASE_USER","fhghjgj" -replace "DATABASE_PASSWORD", "DFGHFHJGJH"
} | Set-Content $_.FullName
}