firstly excuse my self because i'm a newbie and it's realy hard for me to do simple script.
I trying to save some specific files with there directory parent
Exemple : Here i want to save name of "Folder2" "Folder4" and files2, 3, 6.
Folder1\Folder2\
|===> File1
|===> File2
|===> File3
Folder3\Folder4\
|===> File4
|===> File5
|===> File6
For create this tree in my save :
Save\Folder2\
|===> File2
|===> File3
Save\Folder4\
|===> File6
I trying with this :
$Src = "C:\Folder3\"
$Dst = "C:\Save\"
Get-ChildItem -Path $Src -Recurse -Name "File2.txt" | Where-Object {$_.PSIsContainer} | ForEach-Object {[System.IO.Path]::GetDirectoryName($_); New-Item -Path $Dst -Name $_}
I pretty sure it's really easy but i really don't understand how powershell work.
Thanks
If you are filtering so that only containers (directories) are returned You will never have a file object to copy.
If we know more about what you're trying to do we could probably polish this up nicely. So, this is just a start, but try something like:
$SrcRoot = "C:\temp\01-24-20"
$DstRoot = "C:\temp\Save\"
Get-ChildItem -Path $SrcRoot -Recurse -Directory |
ForEach-Object{
$Root = $_ # Makes it easier to reference the object in the outer ForEach loop.
$DstSub = New-Item -Path $DstRoot -Name $Root.Name -ItemType Directory
# > From your initial code it doesn't look like you need recursion here.
# > Also you may want to add the -Filter parameter to add a wildcard pattern
Get-ChildItem $Root.FullName -File |
ForEach-Object{
# > Add an If block below to make the copy conditional.
Copy-Item $_ -Destination $DstSub
}
}
Let us know how it goes.
By why are you setting these this way, when your diagram shows you are also using another folder...
Folder1\Folder2\
... that you are doing nothing within your code but you want files from it.
<#
$Src = "C:\Folder3\"
$Dst = "C:\Save\"
#>
So, then this. Unless that is a typo.
$src1 = 'Folder1\Folder2\'
$Src3 = 'Folder3\Folder4\'
$Dst = 'C:\Save\'
You are only asking for a single file here
# Get-ChildItem -Path $Src -Recurse -Name "File2.txt" |
$src1,$src3 |
ForEach ($File -in (Get-ChildItem -Path $PSItem.FullName) |
This also means, you have no idea where this file is in the tree, so, using the -Recurse to scan all of them. Don't scan if you don't have to. Also, why are you looking for a folder here, when you are only asking for a single file or file set?
Where-Object {
$_.PSIsContainer} |
You are doing a bunch of raw library stuff when there native cmdlets/properties for this sort of thing
ForEach-Object {
# [System.IO.Path]::GetDirectoryName($_)
$PSItem.DirectoryName
';' This is a terminator for string unrelated code on the same line. So, drop that and make this its own line. See why special characters need to be understood here and punctuation statement separators here.
You are also saying you want to save a file to some destination and that does not require the use of New-Item. New-Item is to create a new empty file, but your diagram appears to state you want to move a file or copy a file from the sources
*Folder1\Folder2*
|===> File1
|===> File2
|===> File3
... to
*Save\Folder2*
|===> File2
|===> File3
... and from
*Folder3\Folder4*
|===> File4
|===> File5
|===> File6
... to
*Save\Folder4*
|===> File6
If ($Filename -eq 'File2|File3')
{Copy-Item -Path "$Dst\$($Filename.DirectoryName)" -Force}
}
Your code appears to be something you copied and pasted from some legacy PowerShell level site and tweaked for what your use case. Nothing wrong with using someone else's code, but not if you do not understand it or are not skilled at using the language of the code use case. Don't cause yourself undue issues.
Again, get some training first. It's virtually all free on Youtube, free online books, etc. This will help get your mind and skills set to limit confusion, error, bad code/practices/habits, etc.
So, as you can see there is a good deal out of place in your code vs what you are after. Mostly due to what you say as not really knowing how PowerShell works.
So, along with the other suggested answer(s), try something like the below. Yet, because you've not spent any time studying up on PowerShell, this may not make sense even if it works for you. So, again, jump on Youtube to get some ramp-up first before trying anyone's code.
Just search for:
'Beginning PowerShell'
'Intermediate PowerShell'
'Advanced PowerShell'
'PowerShell file and folder management'
'PowerShell copying files'
'PowerShell moving files'
'PowerShell Loop'
'Learn PowerShell in a Month of Lunches'
Use the built-in help files and the examples there, practice with those and read the help files again:
# See all help topics
Select-Object -Property Name, Synopsis |
Out-GridView -Title 'Select Topic' -OutputMode Multiple |
ForEach-Object { Get-Help -Name $_.Name -ShowWindow }
explorer "$pshome\$($Host.CurrentCulture.Name)"
# get function / cmdlet details
Get-Command -Name Copy-Item -Syntax
(Get-Command -Name Copy-Item).Parameters.Keys
Get-help -Name Copy-Item -Full
Get-help -Name Copy-Item -Online
Get-help -Name Copy-Item -Examples
Get-Command -Name Move-Item -Syntax
(Get-Command -Name Move-Item).Parameters.Keys
Get-help -Name Move-Item -Full
Get-help -Name Move-Item -Online
Get-help -Name Move-Item -Examples
So, in PowerShell, there are always a number of ways to do X or Y thing, some solutions more elegant than others, but here is an example of what I feel you are after. Again, this is a very rough thing and can be tweaked to make it better, or go a different way, based on the use case.
# Clear the screen
Clear-Host
# Set source and destination resources
$src1 = 'D:\Temp\ParentFolder'
$src3 = 'D:\Test\Reference'
$Dst = 'D:\Save'
# Pipe in the source items
$src1,$src3 |
<#
Loop through the source items
#>
ForEach{
<#
Read the source items to locate specific files
#>
Get-ChildItem -Path $PSItem -Include Test_Audio.csv,hello.bat,ParentWELCOM98.WAV -Recurse |
ForEach {
<#
Target only specif cfile names
#>
If($PSitem -match 'Test_Audio.csv|hello.bat')
{
<#
If this is a match then copy to the destination root by adding the directory of the filename
Using teh -WhatIf to validate the action before deciding to remove the -WhatIf for actual taks completion.
#>
Copy-Item -Path $PSitem.FullName -Destination $Dst\$($PSitem.Directory.BaseName) -WhatIf
}
Else
{
<#
If this is a match then copy to the destination root by adding the directory of the filename
Using teh -WhatIf to validate the action before deciding to remove teh -WhatIf for actual taks completion.
#>
Copy-Item -Path $PSitem.FullName -Destination $Dst\$($PSitem.Directory.BaseName) -WhatIf
}
}
}
# Results
<#
What if: Performing the operation "Copy File" on target "Item: D:\Temp\ParentFolder\ParentWELCOM98.WAV Destination: D:\Save\ParentFolder".
What if: Performing the operation "Copy File" on target "Item: D:\Temp\ParentFolder\Test_Audio.csv Destination: D:\Save\ParentFolder".
What if: Performing the operation "Copy File" on target "Item: D:\Test\Reference\hello.bat Destination: D:\Save\Reference".
#>
When you'd remove the -Whatif and use -Force, things will complete and items will be created.
Related
Got another multi-step process I'm looking to streamline. Basically, I'm looking to build a Powershell script to do three things:
Get-Childitem to look for folders with a specific name (we'll call it NAME1 as a placeholder)
For each folder it finds that has the name, I want it to output the full directory to a TXT file (so that in the end I wind up with a text file that has a list of the results it found, with their full paths; so if it finds folders with "NAME1" in five different subdirectories of the folder I give it, I want the full path beginning with the drive letter and ending with "NAME1")
Then I want it to take the list from the TXT file, and copy each file path to another drive and preserve directory structure
So basically, if it searches and finds this:
D:\TEST1\NAME1
D:\TEST7\NAME1
D:\TEST8\NAME1\
That's what I want to appear in the text file.
Then what I want it to do is to go through each line in the text file and plug the value into a Copy-Item (I'm thinking the source directory would get assigned to a variable), so that when it's all said and done, on the second drive I wind up with this:
E:\BACKUP\TEST1\NAME1
E:\BACKUP\TEST7\NAME1
E:\BACKUP\TEST8\NAME1\
So in short, I'm looking for a Get-Childitem that can define a series of paths, which Copy-Item can then use to back them up elsewhere.
I already have one way to do this, but the problem is it seems to copy everything every time, and since one of these drives is an SSD I only want to copy what's new/changed each time (not to mention that would save time when I need to run a backup):
$source = "C:\"
$target = "E:\BACKUP\"
$search = "NAME1"
$source_regex = [regex]::escape($source)
(gci $source -recurse | where {-not ($_.psiscontainer)} | select -expand fullname) -match "\\$search\\" |
foreach {
$file_dest = ($_ | split-path -parent) -replace $source_regex,$target
if (-not (test-path $file_dest)){mkdir $file_dest}
copy-item $_ -Destination $file_dest -force -verbose
}
If there's a way to do this that wouldn't require writing out a TXT file each time I'd be all for that, but I don't know a way to do this the way I'm looking for except a Copy-Item.
I'd be very grateful for any help I can get with this. Thanks all!
If I understand correctly, you want to copy all folders with a certain name, keeping the original folder structure in the destination path and copy only files that are newer than what is in the destination already.
Try
$source = 'C:\'
$target = 'E:\BACKUP\'
$search = 'NAME1'
# -ErrorAction SilentlyContinue because in the C:\ disk you are bound to get Access Denied on some paths
Get-ChildItem -Path $source -Directory -Recurse -Filter $search -ErrorAction SilentlyContinue | ForEach-Object {
# construct the destination folder path
$dest = Join-Path -Path $target -ChildPath $_.FullName.Substring($source.Length)
# copy the folder including its files and subfolders (but not empty subfolders)
# for more switches see https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy
robocopy $_.FullName $dest /XO /S /R:0
}
If you don't want console output of robocopy you can silence it by appending 2>&1, so neither stdout nor stderr is echoed
If you want to keep a file after this with both the source paths and the destinations, I'd suggest doing
$source = 'C:\'
$target = 'E:\BACKUP\'
$search = 'NAME1'
$output = [System.Collections.Generic.List[object]]::new()
# -ErrorAction SilentlyContinue because in the C:\ disk you are bound to get Access Denied on some paths
Get-ChildItem -Path $source -Directory -Recurse -Filter $search -ErrorAction SilentlyContinue | ForEach-Object {
# construct the destination folder path
$dest = Join-Path -Path $target -ChildPath $_.FullName.Substring($source.Length)
# add an object to the output list
$output.Add([PsCustomObject]#{Source = $_.FullName; Destination = $dest })
# copy the folder including its files and subfolders (but not empty subfolders)
# for more switches see https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy
robocopy $_.FullName $dest /XO /S /R:0
}
# write the output to csv file
$output | Export-Csv -Path 'E:\backup.csv' -NoTypeInformation
i am quite new to powershell and i am trying to make a script that copy files to certain folders that are declared in a CSV file. But till now i am getting errors from everywhere and can't find nothing to resolve this issue.
I have this folders and .txt files created in the same folder as the script.
Till now i could only do this:
$files = Import-Csv .\files.csv
$files
foreach ($file in $files) {
$name = $file.name
$final = $file.destination
Copy-Item $name -Destination $final
}
This is my CSV
name;destination
file1.txt;folderX
file2.txt;folderY
file3.txt;folderZ
As the comments indicate, if you are not using default system delimiters, you should make sure to specify them.
I also recommend typically to use quotes for your csv to ensure no problems with accidentally including an entry that includes the delimiter in the name.
#"
"taco1.txt";"C:\temp\taco2;.txt"
"# | ConvertFrom-CSV -Delimiter ';' -Header #('file','destination')
will output
file destination
---- -----------
taco1.txt C:\temp\taco2;.txt
The quotes make sure the values are correctly interpreted. And yes... you can name a file foobar;test..txt. Never underestimate what users might do. 😁
If you take the command Get-ChildItem | Select-Object BaseName,Directory | ConvertTo-CSV -NoTypeInformation and review the output, you should see it quoted like this.
Sourcing Your File List
One last tip. Most of the time I've come across a CSV for file input lists a CSV hasn't been needed. Consider looking at grabbing the files you in your script itself.
For example, if you have a folder and need to filter the list down, you can do this on the fly very easily in PowerShell by using Get-ChildItem.
For example:
$Directory = 'C:\temp'
$Destination = $ENV:TEMP
Get-ChildItem -Path $Directory -Filter *.txt -Recurse | Copy-Item -Destination $Destination
If you need to have more granular matching control, consider using the Where-Object cmdlet and doing something like this:
Get-ChildItem -Path $Directory -Filter *.txt -Recurse | Where-Object Name -match '(taco)|(burrito)' | Copy-Item -Destination $Destination
Often you'll find that you can easily use this type of filtering to keep CSV and input files out of the solution.
example
Using techniques like this, you might be able to get files from 2 directories, filter the match, and copy all in a short statement like this:
Get-ChildItem -Path 'C:\temp' -Filter '*.xlsx' -Recurse | Where-Object Name -match 'taco' | Copy-Item -Destination $ENV:TEMP -Verbose
Hope that gives you some other ideas! Welcome to Stack Overflow. 👋
I want to start by saying coding is a bit outside of my skill set but because a certain problem keeps appearing at work, I'm trying to automate a solution.
I use the below script to read an input file for a list of name, search the C:\ for those files, then write the path to an output file if any are found.
foreach($line in Get-Content C:\temp\InPutfile.txt) {
if($line -match $regex){
gci -Path "C:\" -recurse -Filter $line -ErrorAction SilentlyContinue |
Out-File -Append c:\temp\ResultsFindFile.txt
}
}
I would like to make two modifications to this. First, to search all drives connected to the computer not just C:\. Next, be able to delete any found files. I'm using the Remove-Item -confirm command but so far can't make it delete the file it just found.
So I have danced with this off and on throughout the day and the timeless phrase "There's more than one way to skin a cat" keeps coming to mind so I decided to take to the community.
Scenario:
Source folder "C:\Updates" has 100 files of various extensions. All need to be copied to the sub-folders only of "C:\Prod\" overwriting any duplicates that it may find.
The Caveats:
The sub-folder names (destinations) in "C:\Prod" are quite dynamic and change frequently.
A naming convention is used to determine which sub-folders in the destination need to be excluded when the source files are being copied (to retain the original versions). For ease of explanation lets say any folder names starting with "!stop" should be excluded from the copy process. (!stop* if wildcards considered)
So, here I am wanting the input of those greater than I to tackle this in PS if I'm lucky. I've tinkered with Copy-Item and xcopy today so I'm excited to hear other's input.
Thanks!
-Chris
Give this a shot:
Get-ChildItem -Path C:\Prod -Exclude !stop* -Directory `
| ForEach-Object { Copy-Item -Path C:\Updates\* -Destination $_ -Force }
This grabs each folder (the -Directory switch ensures we only grab folders) in C:\Prod that does not match the filter and pipes it to the ForEach-Object command where we are running the Copy-Item command to copy the files to the directory.
The -Directory switch is not available in every version of PowerShell; I do not know which version it was introduced in off the top of my head. If you have an older version of PowerShell that does not support -Directory then you can use this script:
Get-ChildItem -Path C:\Prod -Exclude !stop* `
| Where-Object { $_.PSIsContainer } `
| ForEach-Object { Copy-Item -Path C:\Updates\* -Destination $_ -Force }
To select only sub folders which do not begin with "!stop" do this
$Source = "C:\Updates\*"
$Dest = "C:\Prod"
$Stop = "^!stop"
$Destinations = GCI -Path $Dest |?{$_.PSIsContainer -and $_.Name -notmatch $Stop }
ForEach ($Destination in $Destinations) {
Copy-Item -Path $Source -Destination $Destination.FullName -Force
}
Edited Now copies all files from Update to subs of Source not beginning with "!stop" The -whatif switch shows what would happen, to arm the script remove the -whatif.
Edit2 Streamlined the script. If also Sub/sub-folders of C:\Prod shall receive copies include a -rec option to the gci just in front of he pipe.
I'm using the following command to copy a directory tree from one folder to another.
Copy-Item $SOURCE $DEST -Filter {PSIsContainer} -Recurse -Force -Verbose
The verbose option is correctly showing each folder that is copied. However, I would like to tell the Verbose option to only shows the first level of the subfolders that are copied. Hence the subfolders/subfolders/... etc wouldn't appear.
Is it possible?
Instead of using the -Verbose option, you could use the -PassThru option to process the successfully processed items via the pipeline. In the following example, I am assuming that $DEST is the existing directory in which the newly copied directory will appear. (You cannot call Get-Item on non-existant objects.)
$SOURCE = Get-Item "foo"
$DEST = Get-Item "bar"
Copy-Item $SOURCE $DEST -Filter {PSIsContainer} -Recurse -Force -PassThru | Where-Object {
# Get the parent object. The required member is different between
# files and directories, which makes this a bit more complex than it
# might have been.
if ($_.GetType().Name -eq "DirectoryInfo") {
$directory = $_.Parent
} else {
$directory = $_.Directory
}
# Select objects as required, in this case only allow through
# objects where the second level parent is the pre-existing target
# directory.
$directory.Parent.FullName -eq $DEST.FullName
}
Count the number of backslashes in the path and add logic to select first level only perhaps. Something like this perhaps?
$Dirs=get-childitem $Source -Recurse | ?{$_.PSIsContainer}
Foreach ($Dir in $Dirs){
$Level=([regex]::Match($Dir.FullName,"'b")).count
if ($Level -eq 1){Copy-Item $Dir $DEST -Force -Verbose}
else{Copy-Item $Dir $DEST -Force}}
*Edited to include looping and logic per requirements
I would suggest using robocopy instead of copy-item. Its /LEV:n switch sounds like it's exactly what you're looking for. Example (you'll need to test & tweak to meet your requirements):
robocopy $source $dest /LEV:2
robocopy has approximately 7 gazillion options you can specify to get some very useful and interesting behavior out of it.