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.
fileinfoto an object that's being returned as such. Have you tried the-Forceswitch? It will create the directory for you.FullNamecast as aStringfirst. I turned it into aFileInfoobject later so I could use the versatility in the pipeline. That the filename is in theDestinationparameter is the result of me using a string replacement in the absolute path of the source file (replacingAppwithBackup) Oh and I always cast everything explicitly, remnant of my coding days. I will try theForceswitch (I think I already did the first time though).