I am working on a script that clones a directory structure (excluding files) to a new directory. Here's my code so-far:
Function Copy-DirectoryStructure{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[string]$Path
)
# Test whether the path is exactly a drive letter
if($Path -match '^[a-zA-Z]:\\$' -or $Path -match '^[a-zA-Z]:$'){
throw "Path cannot be at the root of a drive. Aborting."
}
# Check if the path is valid
if(Test-Path -Path $Path -PathType Container){
# Create the destination path format
$DestinationPath = (get-item $Path).FullName ' Folder Copy'
# Test if our destination already exists.
# If so, prompt for a new name
if(Test-Path -Path $DestinationPath -PathType Container){
Write-Warning "The destination path already exists."
$NewFolderName = Read-Host -Prompt "Input the name of the new folder to be created."
$DestinationPath = (get-item $Path).parent.FullName '\' $NewFolderName
}
# Begin copy
robocopy $Path $DestinationPath /e /xf *.* | Out-Null
} else {
throw "Invalid directory was passed. Aborting."
}
}
Copy-DirectoryStructure -Path "C:\Users\[username]\Desktop\Test"
The relevant part is here:
# Create the destination path format
$DestinationPath = (get-item $Path).FullName ' Folder Copy'
# Test if our destination already exists.
# If so, prompt for a new name
if(Test-Path -Path $DestinationPath -PathType Container){
Write-Warning "The destination path already exists."
$NewFolderName = Read-Host -Prompt "Input the name of the new folder to be created."
$DestinationPath = (get-item $Path).parent.FullName '\' $NewFolderName
}
Right now it prompts the user to input a new name if $DestinationPath already exists and then proceeds to the robocopy. But what if the user inputs a folder name that ALSO already evaluates to an existing path?
I want to handle this scenario gracefully and re-prompt for a new path until the user enters a destination path that doesn't already exist.
I have no idea how to do this.
I know this is an extreme edge-case, but I want to make my code as safe as possible.
Any help is greatly appreciated.
CodePudding user response:
Remove the interactive bits from the function completely - it should simply fail on destination path existing - and instead give the user a way to explicitly pass an alternative destination path:
Function Copy-DirectoryStructure{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[string]$Path,
[Parameter(Mandatory=$false)]
[string]$DestinationPath
)
$ErrorActionPreference = 'Stop'
# Test whether the path is exactly a drive letter
if($Path -match '^[a-zA-Z]:\\$' -or $Path -match '^[a-zA-Z]:$'){
throw "Path cannot be at the root of a drive. Aborting."
}
# Check if the path is valid
if(-not(Test-Path -Path $Path -PathType Container)){
throw "Path must describe an existing directory"
}
# Create the destination path format
if(-not $PSBoundParameters.ContainsKey('DestinationPath'))
{
$DestinationPath = (Get-Item $Path).FullName ' Folder Copy'
}
# Test that the destination isn't an existing file system item
if(Test-Path -Path $DestinationPath){
throw "The destination path already exists."
}
# If we've reached this point all validation checks passed, begin copy
robocopy $Path $DestinationPath /e /xf *.* | Out-Null
}
Now that the function predictably fails, we can use error handling to handle the retry-logic outside the function:
while($true){
$path = Read-Host "Give a target path!"
try {
Copy-DirectoryStructure -Path $path
break
}
catch {
if($_ -match 'The destination path already exists.'){
$destPath = Read-Host "Wanna try an alternative destination path (input NO to start over)"
if($destPath -ceq 'NO'){
continue
}
Copy-DirectoryStructure -Path $path
break
}
}
}