Help respecting directory structure for file copy

Hi - This does almost exactly what I Need it to. It goes through a file that I provide and copies the files. However, it does not respect directory structure and will instead just copy all files to the root of the destination.

Would you please help me solve this so that the files provided in my filename.txt will copy and maintain directory structure.

function FILEBACKUP($filename) { $filename
$file = Get-Content "C:\Source\$filename.txt"
foreach ($line in $file){
$line2=$line -replace "^([^%]+)?%([^%]+)?%(.*)",'$1$env:$2$3'
$line2 = $ExecutionContext.InvokeCommand.ExpandString($line2)
#if ($line -and (test-path ($line2))) {
Copy-Item $line2 "$WFPROFILE\$filename" 
#}
}
}

Hi,

I’m not really sure that I got what you try to do and why you do how you do but some general tips might guide you to a succesfull end.

I assume you did not provide the complete code - you use a variable $WFPROFILE but in the code you show it’s not filled with something.

As far as I think I got it you’re using a text file with structured information to control what your script should do. You get a single line cut it with a regex pattern into pieces, transform it and then use it.
As Powershell is able to deal really easily with structured text you could use

Import-CSV
to get what you need from your text file and feed your loop with it.

Last but not least: think about formatting your code nicely. Specially when you show it to other people. Code indentation helps understanding more easily. And in a few weeks YOU are other people. :wink:

function FILEBACKUP {
	param(
		$Path = "C:\Source\filebackup.txt"
	)
	
    $Files = Import-CSV -Path $Path -Delimiter "%" -Header Option1,Option2,Option3
    
    Foreach ($File in $Files) {
        ## do something with the options ... par example:
        ## $Source = $File.Option1 + $File.Option2 
        ## $Destination = $File.Option2 + $ENV:$File.Option3
        Copy-Item -Path $Source -Destination $Destination
    }
}

… hope it helps
PS:> (79,108,97,102|%{[char]$_})-join’’

I apologize for not posting the entire code. However I will post it now. I am not having problems with the rest of the script just the FILEBACKUP part.

Lets say one of the files I was calling was office 365 - files OK well this script will create a folder based on that file name and then parse through the file and copy the files in the paths of the file IE:

%APPDATA%\Microsoft\Templates*.dotm

The problem is that it does copy the files but it copies them all to the root of the destination. I need to make my FILEBACKUP function respect the entire path. So it should be copying the files to DEST>Microsoft>Templates

#SET WORK FOLDERS VARIABLE
$wfprofile = Join-Path "$env:userprofile" -ChildPath "work folders\profile"

#Build Folders

function BuildFolders(){
$folders = (Get-ChildItem -path C:\Source\).BaseName
foreach ($item in $folders) {
$foldername = $item 
if ($foldername -match "files$") {
if (!(test-path $wfprofile\$foldername)) {new-item -ItemType Directory -Path $wfprofile -Name $foldername}
FILEBACKUP($foldername) }
if ($foldername -match "reg$") {
if (!(test-path $wfprofile\$foldername)) {new-item -ItemType Directory -Path $wfprofile -Name $foldername}
#REGBACKUP($foldername) }
}
}
}

function REGBACKUP($filename) { $filename
$file = Get-Content "C:\Source\$filename.txt"
foreach ($line in $file){
if (test-path ($line -replace "^hkcu\\","hkcu:\")) {
$name = $line -replace "\\","-"
"attempting $line to $WFPROFILE\$filename\$name.reg" 
reg Export $line "$WFPROFILE\$filename\$name.reg" /y
}
else {write-host $line "does not exist"} 
}
}

function FILEBACKUP($filename) { $filename
$file = Get-Content "C:\Source\$filename.txt"
foreach ($line in $file){
$line2=$line -replace "^([^%]+)?%([^%]+)?%(.*)",'$1$env:$2$3'
$line2 = $ExecutionContext.InvokeCommand.ExpandString($line2)
#if ($line -and (test-path ($line2))) {
Copy-Item $line2 "$WFPROFILE\$filename" 
#}
}
}


#CALL FUNCTIONS

BuildFolders
pause
Clear-Host

Take a look at this:

Unfortunately -container that doesn’t work either

hmmm … I’m still not sure if I really got how this should work.

$line2=$line -replace "^([^%]+)?%([^%]+)?%(.*)",'$1$env:$2$3'
$line2 = $ExecutionContext.InvokeCommand.ExpandString($line2)

these two lines look a little strange for me. What’s in these txt files. Could you show some lines from these files?

There are a lot of examples regarding what you are asking. Check out this MCP mag article. I’m just searching for “powershell copy keep folder structure” and there are a lot of examples.

Sorry for the delay in reply, but unfortunately I am still having trouble with this.

I Need to be able to have this copy the files that I provide from an external file source and respect directory structure.

An Example of what I am providing to be copied would be like

%APPDATA%\Microsoft\Office\*.OfficeUI
%APPDATA%\Microsoft\Access
%APPDATA%\Microsoft\Excel
-%APPDATA%\Microsoft\Excel\*.xlsb

Which is why I Have the code

$line2=$line -replace "^([^%]+)?%([^%]+)?%(.*)",'$1$env:$2$3'
$line2 = $ExecutionContext.InvokeCommand.ExpandString($line2)

So that it knows it can expand the %appdata%

This script works beautifully minus that it doesn’t respect directory structure. I need it to respect directory structure.

Please someone help me solve this.

your copy-item is missing -recurse -force, a leading comma in your ‘controler file’ will not work and if you already have …\Microsoft\Excel in your ‘controler file’ another folder like …\Microsoft\Excel*.xlsb does not make sense because it’s already included in the other one

this should work:

$wfprofile = Join-Path "$env:userprofile" -ChildPath "work folders\profile"

function BuildFolders(){
$folders = (Get-ChildItem -path C:\Source\).BaseName
    foreach ($folder in $folders) {
        if ($folder -match "files$") {
            $destination = Join-Path -Path $wfprofile -ChildPath $folder
            if (-not (test-path -Path $destination)) {
                new-item -ItemType Directory -Path $destination
            }
            FILEBACKUP($folder) 
        }
    }
}

function FILEBACKUP($filename) { 
    $sourcefolders = Get-Content "C:\Source\$filename.txt"
    foreach ($sourcefolder in $sourcefolders){
        $sourcepath=$sourcefolder -replace "^([^%]+)?%([^%]+)?%(.*)",'$1$env:$2$3'
        $sourcepath = $ExecutionContext.InvokeCommand.ExpandString($sourcepath)
        Copy-Item $sourcepath "$WFPROFILE\$filename" -Recurse -Force
    }
}

BuildFolders

But may I suggest another approach for you? If you put to your ‘controler file’ paths in powershell format your script could be much simpler.

‘controler file’:

$ENV:APPDATA\Microsoft\Office\*.OfficeUI
$ENV:APPDATA\Microsoft\Access
$ENV:APPDATA\Microsoft\Excel

‘script’:

$Destination = Join-Path -Path $ENV:USERNAME -ChildPath "work folders\profile"
$ControlerFiles = Get-ChildItem -Path "C:\Source" -Filter "*files.txt" -File | Select-Object -ExpandProperty Fullname
foreach ($ControlerFile in $ControlerFiles) {
    $SourcePaths = Get-Content -Path $ControlerFile
    foreach ($SourcePath in $SourcePaths) {
        Copy-Item -Path $SourcePath -Destination $Destination -Recurse -Force
    }
}

unfortunately I cannot edit my own post anymore …
To resolve the paths given by the ‘controler file’ you have to put

$SourcePath = Invoke-Expression  “”“$SourcePath”“”
before the copy-item cmdlet

Unfortunately this still does not work.

1: If I change my copy-item line to include -recurse -force it still does not respect directory structure. Example I am copying the files ending in *.officeUI which live in the office folder. But they don’t get copied to the office folder, the go to the root of the destination.

2: If I try the other way and using the $ENV:APPDATA it completely errors out and says it can’t find a drive matching $ENV:APPDATA

1: If I change my copy-item line to include -recurse -force it still does not respect directory structure. Example I am copying the files ending in *.officeUI which live in the office folder. But they don't get copied to the office folder, the go to the root of the destination.
Of course Powershell will not copy the folder if you specify the files to be copied. ;-)

Maybe you have to change your controler file to provide the folder you are looking for AND the file pattern:
‘controler file’:

path,filepattern
$ENV:APPDATA\Microsoft\Office,*.officeUI
$ENV:APPDATA\Microsoft\Access,*
$ENV:APPDATA\Microsoft\Excel,*

So you have to use import-csv to get the information about what you want to backup:
‘backup script’

$Destination = Join-Path -Path $ENV:USERPROFILE -ChildPath "work folders\profile"
$ControlerFiles = Get-ChildItem -Path "C:\Source" -Filter "*files.txt" -File | Select-Object -ExpandProperty Fullname
foreach ($ControlerFile in $ControlerFiles) {
    $Sources = Import-Csv -Delimiter "," -Path $ControlerFile
    foreach ($Source in $Sources) {
        $SourcePath = Invoke-Expression  """$($Source.path)"""
        $FilePattern = $Source.filepattern
        Copy-Item -Path $SourcePath -Filter $FilePattern -Destination $Destination -Recurse -Force
    }
}

That should catch the folder with the files you’re looking for.