How can I run a powershell script in a for loop? - powershell

I have been tasked to sort home video and pictures for my family. The scripts that I have work fine but I have to manually run them against each directory. How can I run my script against all child-items that are only at 1 Depth?
My current crude scripts are as follows:
$current_dir = Split-Path -Path $pwd -Leaf
$new_path = "H:\sorted\$current_dir\pictures"
Get-ChildItem -R . -Include ('*.jpg', '*.jpeg', '*.png') | Move-Item -Destination (New-Item -Force -Path "$new_path" -Type Directory)
$current_dir = Split-Path -Path $pwd -Leaf
$new_path = "H:\sorted\$current_dir\videos"
Get-ChildItem -R . -Include ('*.mp4', '*.mkv', '*.3pg','*.flv', '*.mov', '*.gif') | Move-Item -Destination (New-Item -Force -Path "$new_path" -Type Directory)
Example file tree
E:.
├───April9383
├───April98765
│ └───carson
├───Cathy
├───Cathy(1)
├───Charlie
│ ├───Photos
│ └───Videos
├───daleville
I want the end structure to look like Charlie does in the example. How can I run both of these with a loop from E: ?
I have tried
$sub_dir = $(Get-ChildItem . -Depth 1)
foreach ($sub in $sub_dir) {
picture-sort.ps1
}
but this took the name of the folder that all of the example files were stored in and not that of "April9383" etc
SOLVED:
I ended up going with #Santiago's response but edited a bit as it wasn't working exactly how I needed it.
I took this and ran with it to end up with
$base = "E:\sorted"
$current_dir = $pwd
# get the folders 1 level deep and enumerate
Get-ChildItem . -Depth 0 -Directory | ForEach-Object {
# join the destination with this folder's Name
$path = Join-Path $base -ChildPath $_.Name
$pic_path = "$path\pictures"
$vid_path = "$path\videos"
#source dirs
$stripped_dest_path = Split-Path -Path $path -Leaf
$src_path = Join-Path $current_dir $stripped_dest_path
#Print statement
Write-Output "Copying pictures from $src_path to $pic_path"
# get and move all pictures
Get-ChildItem -R $src_path -Include ('*.jpg', '*.jpeg', '*.png') |
Copy-Item -ErrorAction SilentlyContinue -Destination (New-Item -Force -Path "$pic_path" -Type Directory)
#Print statement
Write-Output "Copying videos from $src_path to $vid_path"
# get and move all videos
Get-ChildItem -R $src_path -Include ('*.mp4', '*.mkv', '*.3pg','*.flv', '*.mov', '*.gif', '*.avi') |
Copy-Item -ErrorAction SilentlyContinue -Destination (New-Item -Force -Path "$vid_path" -Type Directory)
Write-Output "END OF LOOP"
}

You're almost there, just need to put all your logic inside the loop:
$base = "H:\sorted"
# get the folders 1 level deep and enumerate
Get-ChildItem . -Depth 1 -Directory | ForEach-Object {
# join the destination with this folder's Name
$path = Join-Path $base -ChildPath $_.Name
# get and move all pictures
$_ | Get-ChildItem -Recurse -Include '*.jpg', '*.jpeg', '*.png' |
Move-Item -Destination (New-Item -Path (Join-Path $path -ChildPath 'Pictures') -Type Directory -Force)
# get and move all videos
$_ | Get-ChildItem -Recurse -Include '*.mp4', '*.mkv', '*.3pg','*.flv', '*.mov', '*.gif' |
Move-Item -Destination (New-Item -Path (Join-Path $path -ChildPath 'videos') -Type Directory -Force)
}

I ended up going with #Santiago's response but edited a bit as it wasn't working exactly how I needed it.
I took this and ran with it to end up with
$base = "E:\sorted"
$current_dir = $pwd
# get the folders 1 level deep and enumerate
Get-ChildItem . -Depth 0 -Directory | ForEach-Object {
# join the destination with this folder's Name
$path = Join-Path $base -ChildPath $_.Name
$pic_path = "$path\pictures"
$vid_path = "$path\videos"
#source dirs
$stripped_dest_path = Split-Path -Path $path -Leaf
$src_path = Join-Path $current_dir $stripped_dest_path
#Print statement
Write-Output "Copying pictures from $src_path to $pic_path"
# get and move all pictures
Get-ChildItem -R $src_path -Include ('*.jpg', '*.jpeg', '*.png') |
Copy-Item -ErrorAction SilentlyContinue -Destination (New-Item -Force -Path "$pic_path" -Type Directory)
#Print statement
Write-Output "Copying videos from $src_path to $vid_path"
# get and move all videos
Get-ChildItem -R $src_path -Include ('*.mp4', '*.mkv', '*.3pg','*.flv', '*.mov', '*.gif', '*.avi') |
Copy-Item -ErrorAction SilentlyContinue -Destination (New-Item -Force -Path "$vid_path" -Type Directory)
Write-Output "END OF LOOP"
}

This should work:
$PictureFiles = #('*.jpg', '*.png')
$VideoFiles = #('*.mkv', '*.mp4')
$MyFiles = Get-ChildItem H:\sorted -Recurse -File -Include #($PictureFiles + $VideoFiles) -Depth 1
foreach ($File in $MyFiles) {
#Videos
if ($File.Extension -in ($VideoFiles -replace '\*')) {
mkdir ($File.DirectoryName + "\Videos\") -Force
Move-Item -Path $File.FullName -Destination ($File.DirectoryName + "\Videos\" + $File.Name) -Force
}
#Pictures
if ($File.Extension -in ($PictureFiles -replace '\*')) {
mkdir ($File.DirectoryName + "\Pictures\") -Force
Move-Item -Path $File.FullName -Destination ($File.DirectoryName + "\Pictures\" + $File.Name) -Force
}
}

Related

How to log copied items during the backup script?

I need to make basic / or more advanced backup script that would copy items from folder A to folder B and then log what it did.
This copies the files just fine:
$source = 'path\gamybinis\*'
$dest = 'path\backup'
Get-ChildItem -Path $source -Recurse | Where-Object { $_.LastWriteTime -gt [datetime]::Now.AddMinutes(-5)
}| Copy-Item -Destination $dest -Recurse -Force
Write-Host "Backup started"
Pause
But after this I can't write the log with | Out-File, So I've tried this:
$source = 'path\gamybinis\*'
$dest = 'path\backup'
$logFile = 'path\log.txt'
$items = Get-ChildItem -Path $source -Recurse | Where-Object { $_.LastWriteTime -gt [datetime]::Now.AddMinutes(-5)
}
foreach($item in $items){
Out-File -FilePath $logFile -Append
Copy-Item -Path "$source\$item" -Destination $dest -Recurse -Force
}
Write-Host "Backup started"
Pause
This one does absolutely nothing, what exactly am I doing wrong?
(Advanced script part would be: backing up recently modified files then files should be archived to .rar/.zip, log file have to have structure that is easily readable and log file should have information which user was working on the device during the backup) - For those who are wondering.
If you can't use robocopy, in pure PowerShell code you could do this
$source = 'path\gamybinis' # no need for '\*' because you're specifying -Recurse
$dest = 'path\backup'
$logFile = 'path\log.txt'
# test if the destination path exists. If not, create it first
if (!(Test-Path -Path $dest -PathType Container)) {
$null = New-Item -Path $dest -ItemType Directory
}
Write-Host "Backup started"
Get-ChildItem -Path $source -Recurse |
Where-Object { $_.LastWriteTime -gt (Get-Date).AddMinutes(-5) } |
ForEach-Object {
$_ | Copy-Item -Destination $dest -Recurse -Force
Add-Content -Path $logFile -Value "$((Get-Date).ToString("yyyy-MM-dd HH:mm:ss")) - Copied file '$($_.FullName)'"
}
Write-Host "Backup done"
From your comments, I understand you have problems when using the -Container switch.
Below code does not use that and creates the folder structure of the copied files in the backup folder, strictly using Powershell code:
$source = 'path\gamybinis' # no need for '\*' because you're specifying -Recurse
$dest = 'path\backup'
$logFile = 'path\log.txt'
Write-Host "Backup started"
Get-ChildItem -Path $source -File -Recurse |
Where-Object { $_.LastWriteTime -gt (Get-Date).AddMinutes(-5) } |
ForEach-Object {
$target = Join-Path -Path $dest -ChildPath $_.DirectoryName.Substring($source.Length)
if (!(Test-Path $target -PathType Container)) {
# create the folder if it does not already exist
$null = New-Item -Path $target -ItemType Directory
}
$_ | Copy-Item -Destination $target -Force
Add-Content -Path $logFile -Value "$((Get-Date).ToString("yyyy-MM-dd HH:mm:ss")) - Copied file '$($_.FullName)'"
}
Write-Host "Backup done"

Batch Replace-Name & Move-item

I got a script:
$CopySource = "C:\Source\Qlikview Storage\PrivateData\Gemensamma\Qvd_Raw\Agresso"
$CopyDestination = "C:\Dest\Qlikview Storage\PrivateData\Gemensamma\Qvd_Raw\Agresso"
$files = Get-ChildItem -File "*.qvd" $CopySource -Force
foreach ($file in $files) {
Copy-Item -path $CopySource\$_$file -Destination $CopyDestination -Force
}
$CopySource = "C:\Source\Qlikview Storage\PrivateData\Gemensamma\Qvd_Raw\Agresso"
$CopyDestination = "C:\Dest\Qlikview Storage\PrivateData\Gemensamma\Qvd_Raw\Agresso"
$items = Get-ChildItem -File "*.qvd" $CopyDestination
foreach($I in $Items) {
$newfilename= rename-item -path $I.Name -newname ("Agresso_" + $I.Name)
The problem I have is that I need to copy files from source to destination and once they are over they need to have agresso_ added. Then they day after a batch of files will be copied again and also they need to be renamed to agresso_ and overwrite the old ones, preferably with move-item. I got a problem already, as I am not used with prefixes,
I tried enless of versions with where-object and simular, could not figure out a way to use test-path either.
I did exactly this but with renaming the files, like this for maximo:
$files = Get-ChildItem -File "*.qvd" $CopySource -Force
foreach ($file in $files) {
Copy-Item -path $CopySource\$_$file -Destination $CopyDestination -Force -Verbose 4>&1 |
Out-File -Append $logpath
}
"Klar med Kopiering av .qvd filer $global:currenttime" | Out-File $logpath -Append
"Påbörjar omdöpning av .qvd filer $global:currenttime" | Out-File $logpath -Append
$items = Get-ChildItem -File "*.qvd" $CopyDestination
foreach($I in $Items) {
$newfilename=$I.Name.Replace("QVDLager1","Maximo")
Move-Item -Path $I.FullName -Destination $CopyDestination\$newfilename -Force -Verbose 4>&1 |
Out-File -Append $logpath
If anyone can help me in the right direction it would be highly appriciated.
You can copy (or move) and rename the destination at the same time:
$CopySource = "C:\Source\Qlikview Storage\PrivateData\Gemensamma\Qvd_Raw\Agresso"
$CopyDestination = "C:\Dest\Qlikview Storage\PrivateData\Gemensamma\Qvd_Raw\Agresso"
Get-ChildItem -Path $CopySource -File "*.qvd" -Force | ForEach-Object {
# create the full path for the target file with "Agresso_" prefixed
$target = Join-Path -Path $CopyDestination -ChildPath ('Agresso_{0}' -f $_.Name)
$_ | Copy-Item -Destination $target -WhatIf
}
If satisfied with the results of the code shown in the console, you can remove the -WhatIf switch to really start copying (or moving) the files.

Powershell copy only selected files with folder structure

I have a folder hierarchy with a lot of files.
I need to copy all folders and only selected files. For this purposes I write script:
$path = "D:\Drop\SOA-ConfigurationManagement - Test\181"
$files = Get-ChildItem -Path $path -Recurse | ? { $_.Name -like "system.serviceModel.client.config" }
$Destination = "D:\test\"
Copy-Item $files -Destination $Destination -recurse
When I execute variable $files, it returns correct path:
But when I execute Copy-Item it returns not full path:
Perhaps my approach is wrong. If so, how to copy entire folder structure, and only selected files (in this case system.serviceModel.client.config file)?
UPD1 Ok, I've found, how to copy only folders:
$path = "D:\Drop\SOA-ConfigurationManagement - Test\181\"
$Destination = "D:\test\"
Copy-Item $path $Destination -Filter {PSIsContainer} -Recurse -Force
But how to copy only selected files, preserving their location? What needs to be in $Destination variable?
$files = Get-ChildItem -Path $path -Recurse | ? { $_.Name -like "system.serviceModel.client.config" } | % { Copy-Item -Path $_.FullName -Destination $Destination }
This code would keep the directory structure the same too
$path = "D:\Drop\SOA-ConfigurationManagement - Test\181\"
$Destination = "D:\test\"
$fileName = "system.serviceModel.client.config"
Get-ChildItem -Path $path -Recurse | ForEach-Object {
if($_.Name -like $fileName) {
$dest = "$Destination$(($_.FullName).Replace($path,''))"
$null = New-Item $dest -Force
Copy-Item -Path $_.FullName -Destination $dest -Force
}
}
To copy the whole folder structure AND files with a certain name, below code should do what you want:
$Source = 'D:\Drop\SOA-ConfigurationManagement - Test\181'
$Destination = 'D:\test'
$FileToCopy = 'system.serviceModel.client.config'
# loop through the source folder recursively and return both files and folders
Get-ChildItem -Path $Source -Recurse | ForEach-Object {
if ($_.PSIsContainer) {
# if it's a folder, create the new path from the FullName property
$targetFolder = Join-Path -Path $Destination -ChildPath $_.FullName.Substring($Source.Length)
$copyFile = $false
}
else {
# if it's a file, create the new path from the DirectoryName property
$targetFolder = Join-Path -Path $Destination -ChildPath $_.DirectoryName.Substring($Source.Length)
# should we copy this file? ($true or $false)
$copyFile = ($_.Name -like "*$FileToCopy*")
}
# create the target folder if this does not exist
if (!(Test-Path -Path $targetFolder -PathType Container)) {
$null = New-Item -Path $targetFolder -ItemType Directory
}
if ($copyFile) {
$_ | Copy-Item -Destination $targetFolder -Force
}
}
try this
$path = 'D:\Drop\SOA-ConfigurationManagement - Test\181\'
$Destination = 'D:\test\'
$files = Get-ChildItem -Path $path -Recurse -File | where Name -like "*system.serviceModel.client.config*" | %{
$Dir=$_.DirectoryName.Replace($path, $Destination)
$NewPAthFile=$_.FullName.Replace($path, $Destination)
#create dir if not exists
New-Item -Path $Dir -ItemType Directory -Force -ErrorAction SilentlyContinue
#copy file in new dir
Copy-Item $_.FullName $NewPAthFile
}
With minimal changes I'd suggest the following:
$path = "D:\Drop\SOA-ConfigurationManagement - Test\181"
$files = Get-ChildItem -Path $path -Recurse | ? { $_.Name -like "system.serviceModel.client.config" }
$Destination = "D:\test\"
$files | % { $_ | Copy-Item -Destination $Destination -recurse }
You can even put the whole copy on one line:
$path = "D:\Drop\SOA-ConfigurationManagement - Test\181"
$Destination = "D:\test\"
Get-ChildItem -Path $path -Recurse | ? { $_.Name -like "system.serviceModel.client.config" } | % { $_ | Copy-Item -Destination $Destination -recurse }
Copy-Item can find the path from the stream of input objects but it doesn't seem to be able to take a collection of System.IO.FileInfo objects as an argument to Path.

Exclude folder and files when using get-childitem in powershell

I'm not sure how I should be excluding the following:
.env, web.config, node_modules
$sourceRoot = "C:\Users\Wade Aston\Desktop\STEMuli\server"
$destinationRoot = "C:\Users\Wade Aston\Desktop\STEMuli/server-sandbox"
$dir = get-childitem $sourceRoot -Exclude .env, web.config, node_modules <-- How do I exclude these
$i=1
$dir| %{
[int]$percent = $i / $dir.count * 100
Write-Progress -Activity "Copying ... ($percent %)" -status $_ -PercentComplete $percent -verbose
$_ | copy -Destination $destinationRoot -Recurse -Force
$i++
}
Thank you =]
a couple of notes:
* Try using wildcards, to apply exclusion, like: *.env
* Copy-Item parameter Source, allows using collection of type String. Using collection should be faster, than processing sequential with foreach!
* If you need only the files, you may consider using Get-ChildItem -File
You may try something like:
$Source = Get-ChildItem -Path C:\TEMP -Exclude dism.log, *.csv
$dest = 'C:\temp2'
Copy-Item -Path $Source -Destination $dest -Force
Hope it helps!
To exclude both certain files like '*.env' and 'web.config' AND also exclude a folder with a certain name, you could do this:
$sourceRoot = "C:\Users\Wade Aston\Desktop\STEMuli\server"
$destinationRoot = "C:\Users\Wade Aston\Desktop\STEMuli\server-sandbox"
$dir = Get-ChildItem -Path $sourceRoot -Recurse -File -Exclude '*.env', 'web.config' |
Where-Object{ $_.DirectoryName -notmatch 'node_modules' }
$i = 1
$dir | ForEach-Object {
[int]$percent = $i / $dir.count * 100
Write-Progress -Activity "Copying ... ($percent %)" -Status $_ -PercentComplete $percent -Verbose
$target = Join-Path -Path $destinationRoot -ChildPath $_.DirectoryName.Substring($sourceRoot.Length)
# create the target destination folder if it does not already exist
if (!(Test-Path -Path $target -PathType Container)) {
New-Item -Path $target -ItemType Directory | Out-Null
}
$_ | Copy-Item -Destination $target -Force
$i++
}

Move files into alphabetically named folders

Just wondering if it's possible to have a script move film files to a specific folder based on the alphabet?
Eg Scream 4 would get moved to e:\movies\s\
Avatar would get moved to e:\movies\a\
I start a script that looks like this :
But the result is not good!The script try to create a directory with files name...
$a = new-object -comobject wscript.shell
$b = Get-Location
foreach($file in (dir $b -file -recurse)) {
New-Item -Path $b -Name (Split-Path $file.fullname -Leaf).Replace($file.extension,"") -ItemType Directory -Confirm
Move-Item -Path $file.fullname -Destination "$b\$((Split-Path $file.fullname -Leaf).Replace($file.Extension,''))" -Confirm
}
An idea ?
Many thanks!
Kreg
Destination folders must exist before you run the command:
dir $b -file -recurse | Move-Item -Destination {"e:\movies\$($_.Name[0])"}
This will create the folders at run time:
dir $b -File -Recurse | foreach{
$folder = Join-Path e:\movies $_.Name[0]
md $folder -force | Out-Null
$_ | Move-Item -Destination $folder
}