help create function to backup a fileset

I work with a lot of time-stamped CSV files generated automatically by a system we have. I’d like to write scripts to manipulate these files to compare system status from one moment to the next, but first I want to copy them as a backup in case I want to revert changes.

I’ve decided to create a Backup-File function in my $profile to call in all my scripts, I hope that’s the right move. I would like to create a function that works for any filetype, not just CSVs. In theory it would take all the files in the working directory and copy them into a subfolder therein, adding a " (Copy #)" to the end of the filename …before the extension… to preserve all copies. I spent all damn day working on this and I’ve come up with a nice chunk of code that just doesn’t seem to work.

I can select parts of it and walk through the script in parts, without getting an error, and I see the files being created properly. But when I run the whole thing, it doesn’t do anything!

I’ve even gone so far as to start putting in Write-Debug lines but those don’t work either unless I select those smaller blocks individually.

To test this script, create a temp folder with some files of any type, run the script in that directory, and see if the files you created were copied to a new subfolder under your temp folder. Run it again, to see if a 2nd set is created with different names.

function Backup-File ([string]$BkpFolder = "Backup", [string]$FileType = "*")
{
$DebugPreference = "Continue" 

    # Create target path if it doesn't exist
    {
        if (-not (Test-Path $BkpFolder))
        {
            New-Item -Path $BkpFolder -ItemType directory |out-null 
        }
    } # END ... path if it doesn't exist

    # Master object to variable
    {
        $FileObjects = Get-ChildItem ("*." + [string]$FileType) | Sort-Object Length -Descending
        
    } # END ... to variable

    # Introduce variables for backup file copy operation, so the destination can be different if needed. 
    {
        
        [int]$i = $FileObjects.Count - 1 
        While ($i -gt -1) {
            Write-Debug 'While $i > -1 ... Setting 3 variables'
            Set-Variable -Name ("SourceFileObject"      + [string]$i) -Value $FileObjects[$i]
            Set-Variable -Name ("DestinationFilename" + [string]$i) -Value $FileObjects[$i].Name
            Set-Variable -Name ("DestinationFilePath" + [string]$i) -Value (".\" + ($BkpFolder) + "\" + ($FileObjects[$i]).Name)

            # Incrementally rename the file instead of overwriting any existing. 
            {
                # Check to see if the dest filename matches the path filename of the source
                If (Test-Path -Path ((Get-Variable -Name ("DestinationFilePath" + [string]$i)).Value)) 
                {
                Write-Debug 'If $DestinationFilename# exists in target ... find a new name'
                    # Iterate until you have a unique filename
                    $j = 2
                    While (Test-Path -Path ((Get-Variable -Name ("DestinationFilePath" + [string]$i)).Value)) 
                    {
                         Write-Debug 'While $DestinationFilename# exists in target ... change name'
                         $copyJ = (" (Copy " + [string]$j + ")")
                         Set-Variable -Name ("DestinationFilePath" + [string]$i) `
                                      -Value (".\" + ($BkpFolder) + "\" `
                                               + ($FileObjects[$i].Name).TrimEnd($FileObjects[$i].Extension) `
                                               + $copyJ + $FileObjects[$i].Extension)
                    $j += 1
                    } 
                }
            Write-Debug 'Copying file to target'
            Copy-Item -Path $FileObjects[$i] `
                      -Destination ((Get-Variable -Name ("DestinationFilePath" + [string]$i)).Value) `
                      -Force

            } # END     rename the file instead of overwriting any existing. 
        Write-Debug 'Done copying ... Subtract 1 from $i and loop. '
        $i -= 1
        
        }
    } # END variables for backup file copy operation, so the destination can be different if needed.  


 Write-Debug "End of function" 
 } # END Function  

Also, It’s been a long day for my brain and I may not be choosing the best implementation do accomplish my goals.

Also wondering if I should I have used a cmdlet instead of a function?

Thanks. :slight_smile:

Hey Tony,

Maybe it’s the auto formatting of the code, but it’s not working for me at all. There are additional curly brackets in the code which are not used for scriptblocks, and this is causing what should be code to be seen as text, which is then output to the screen, e.g.
Line 6 and its counterpart.

If it’s not how you’ve written it, and the formatting has added it, could you paste the code in again as normal text?

Ok yeah, rookie mistake. I was using those { }'s for housekeeping. I just prefaced them all with an Invoke-Command and it’s working (sorta) now. Better at least, needs more debugging.

I’m gonna leave this one open for a day or two, in case I hit another roadblock. It almost seems like I may be reinventing the wheel. I’ve looked on yonder Internets for a script that does this but nothing I found handles what I want, all told.

Thanks, Tim…

Latest code for curious folks:

function Backup-File ([string]$BkpFolder = "Backup", [string]$FileType = "*")
{
$DebugPreference = "Continue" 

    # Create target path if it doesn't exist
    Invoke-Command {
    
        if (-not (Test-Path $BkpFolder))
        {
            New-Item -Path $BkpFolder -ItemType directory |out-null 
        }
    } 
    # END ...target path if it doesn't exist

    # Master object to variable
    Invoke-Command {
        $FileObjects = Get-ChildItem ("*." + [string]$FileType) | Sort-Object Length -Descending
        
    } 
    # END       ... to variable

    # Introduce variables for backup file copy operation, so the destination can be different if needed. 
    Invoke-Command {
        
        
        # [int]$i = $FileObjects.Count - 1 
        
        
        Write-debug ('File variable set $i. ' + " i = $i")
        While ($i -gt -1) {
            Write-Debug ('While $i > -1 ... Setting 3 variables.   ' + " i = $i")
            Set-Variable -Name ("SourceFileObject"      + [string]$i) -Value $FileObjects[$i]
            Write-Debug "SourceFileObject = $SourceFileObject"
            Set-Variable -Name ("DestinationFilename" + [string]$i) -Value $FileObjects[$i].Name
            Write-Debug "DestinationFilename = $DestinationFilename"
            Set-Variable -Name ("DestinationFilePath" + [string]$i) -Value (".\" + ($BkpFolder) + "\" + ($FileObjects[$i]).Name)
            Write-Debug "DestinationFilePath = $DestinationFilePath"

            Write Debug "Incrementally rename $DestinationFilePath instead of overwriting any existing. "
            Invoke-Command {
                Write Debug "Check to see if $DestinationFilePath matches the filename of $FileObjects[$i]."
                If (Test-Path -Path ((Get-Variable -Name ("DestinationFilePath" + [string]$i)).Value)) 
                 {
                Write-Debug 'If $DestinationFilename# exists in target ... find a new name'
                    # Iterate until you have a unique filename
                    $j = 2
                    While (Test-Path -Path ((Get-Variable -Name ("DestinationFilePath" + [string]$i)).Value)) 
                    {
                         Write-Debug 'While $DestinationFilename# exists in target ... change name'
                         $copyJ = (" (Copy " + [string]$j + ")")
                         Set-Variable -Name ("DestinationFilePath" + [string]$i) `
                                      -Value (".\" + ($BkpFolder) + "\" `
                                               + ($FileObjects[$i].Name).TrimEnd($FileObjects[$i].Extension) `
                                               + $copyJ + $FileObjects[$i].Extension)
                    $j += 1
                    } 
                }
            Write-Debug 'Copying file to target'
            Copy-Item -Path $FileObjects[$i] `
                      -Destination ((Get-Variable -Name ("DestinationFilePath" + [string]$i)).Value) `
                      -Force

            } 
            # END       ... rename the file instead of overwriting any existing. 
        Write-Debug 'Done copying ... Subtect 1 from $i and loop. '
        $i -= 1
        
        }
    } 
    # END                 ... backup file copy operation, so the destination can be different if needed.  


 Write-Debug "End of function" 
 } # END Function  

Your script appears to be overcomplicated with all of the Invoke-Commands and Set and Get-Variables. Take a look at this approach and see if it meets your requirements:

$source = "C:\Test\*"
$destination = "C:\Backup"

if (!(Test-Path -Path $destination)) {
    #Create the backup directory if doesn't exist
    New-Item -Path $destination -ItemType Directory -Force
    #Also create a text file so $destinationFiles isn't null
    New-Item -Path $destination -Name "test.txt" -Force -ItemType File
}

#Get files from the source and desination paths
$sourceFiles = Get-ChildItem -Path $source -Include "*.txt"
$destinationFiles = Get-ChildItem -Path ($destination + "\*") -Include "*.txt"

#Compare the source and destination and if exists only in the source, copy it to the destination 
Compare-Object -ReferenceObject $sourceFiles -DifferenceObject $destinationFiles -Property FullName -PassThru |
Where {$_.SideIndicator -eq "<="} |
foreach {
    Copy-Item -Path $_.FullName -Destination ($destination + "\" + $_.Name) -WhatIf
}

Thanks Rob! I didn’t see your reply there before reworking my script from scratch myself. You’re right. It looks a lot better now. Added some Verbose language as well.

Can’t figure out how to post the full function. There are 's that end up screwing up the code. If there’s a trick to that, someone pls let me know.

Edit: apparently it’s a wordpress issue with backticks…