0

I am trying to create a little patcher in PowerShell which first backs up the files before replacing them. But I can't get it to cooperate when the file is in a subdirectory. To simplify:

First, it creates the backup directory: $Null = New-Item -Path 'C:\Backup' -ItemType 'Directory'

Next, I check for the file: [System.IO.FileInfo]$FileInfo = Get-Item -LiteralPath 'C:\App\bin\executable.exe'

Now, I want this file to end up in C:\Backup\bin\executable.exe

The first thing I tried was to get the FullName and replace C:\App with C:\Backup resulting in $FileInfo | Copy-Item -Destination 'C:\Backup\bin\executable.exe'

But it keeps throwing an exception because C:\Backup\bin does not exist. I tried combinations of the -Recurse and -Container switches.

I assume the issue is that I'm setting the full path as my destination, resulting in no relative paths being created. But I don't see another way to get that target path set. I can't tell it to copy to C:\Backup\bin because there's no logical way for me to know about bin without extracting it. At which point I might as well create it in the backup directory.

But I kind of want this to be automagic (for the C:\App\multi\level\path\file.dll at a later stage). This makes the script more flexible if I change the file locations later on.

I'm probably missing something really obvious here. Any suggestions?


This is what I'm doing now, compensating for having to make the parent directories myself. I'm open to ways to optimize and of course still looking for a way to not have to explicitly create the parent directories.

Function Backup-Item {
  Param (
    [Parameter(Mandatory = $True)] [System.String]$Source,
    [Parameter(Mandatory = $True)] [System.String]$SourceRoot,
    [Parameter(Mandatory = $False)][System.Management.Automation.SwitchParameter]$Backup = $True,
    [Parameter(Mandatory = $False)][System.String]$BackupDirectory
  ) # Param

  Begin {
    If (Test-Path -LiteralPath $Source) { [System.IO.FileInfo]$SourceFile = Get-Item -LiteralPath $Source }
    Else { } # TODO: Break

    If ($Backup) {
      If (Test-Path -LiteralPath $BackupDirectory) { } # TODO: Break
      Else { $Null = New-Item -Path $BackupDirectory -ItemType 'Directory' }

      [System.String]$Destination = $SourceFile.FullName.Replace($SourceRoot,$BackupDirectory)
    } # If
  } # Begin

  Process {
    If ($Backup) {
      [System.String]$TargetDirectory = $SourceFile.DirectoryName.Replace($SourceRoot,$BackupDirectory)
      If (-not (Test-Path -LiteralPath $TargetDirectory)) { $Null = New-Item -Path $TargetDirectory -ItemType 'Directory' }

      $SourceFile | Copy-Item -Destination $Destination
    } # If
  } # Process

  End {}
} # Function Backup-Item

I don't like having to provide the SourceRoot but it's the only way to deduce the relative paths I could think of.

I will also need to make BackupDirectory mandatory but only if Backup is True (which is the default and a bit dirty since a switch should be off by default, but I wanted the ability to do -Backup:$False to override).

I would use this as a helper script in the larger patcher-script.

2
  • You don't need to provide the name again when copying a file. It's also redundant to cast a type of fileinfo to an object that's being returned as such. Have you tried the -Force switch? It will create the directory for you. Commented Dec 14, 2021 at 12:42
  • I started with the FullName cast as a String first. I turned it into a FileInfo object later so I could use the versatility in the pipeline. That the filename is in the Destination parameter is the result of me using a string replacement in the absolute path of the source file (replacing App with Backup) Oh and I always cast everything explicitly, remnant of my coding days. I will try the Force switch (I think I already did the first time though). Commented Dec 14, 2021 at 13:42

1 Answer 1

2

Why not use robocopy to copy the entire C:\Apps folder to the C:\Backup folder, like with

robocopy C:\Apps C:\Backup /MIR

If you want pure PowerShell, you can do

$sourcePath = 'C:\Apps'
$backupPath = 'C:\Backup'
Get-ChildItem $sourcePath -File -Recurse | ForEach-Object {
    # create the destination folder, even if it is empty
    $targetFolder = Join-Path -Path $backupPath -ChildPath $_.DirectoryName.Substring($sourcePath.Length)
    $null = New-Item -ItemType Directory -Path $targetFolder -Force
    # next copy the file
    $_ | Copy-Item -Destination $targetFolder
}
Sign up to request clarification or add additional context in comments.

1 Comment

I am looking for a PoSh native solution. RIght now I'm extracting the full path, replacing C:\App with C:\Backup and then creating those directories first. But it seems so strange that there's switch or similar which can do it for you (copy and create parents if those don't exist).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.