Morpheus5150
Morpheus5150

Reputation: 21

Powershell Replace not working with user input

The below code works perfect but If i try to get user input for $source:

$Source= Read-Host -Prompt "Enter the source directory (example c:\dir1)"

The Replace does not work. It keeps it as $source instead of making it Dir2 Can anyone help me figure this out? I have been messing with it for a couple hours now

$Source = "C:\Dir1" 
$Destination = "C:\Dir2" 
$LogfilePath = $Destination + "\Log.txt"
    
Get-ChildItem -Path $Source -File -Recurse | ForEach-Object {
    $NewDir = $_.DirectoryName.Replace($Source, $Destination) 
    #see if destination directory exists
    if (-not(Test-Path -Path $NewDir)) {
        New-Item -Path $NewDir -ItemType Directory | Out-Null   
    }
    Copy-Item -Path $_.FullName -Destination $NewDir
    Write-Host '.' -NoNewline
    #added to validate time stamp in log file is working
    Start-Sleep -Seconds 1
    "$([DateTime]::Now) copied - '$($_.name)' from '$($_.DirectoryName)' to '$NewDir' Size: $([System.Math]::Round(($($_.Length)/1KB),2))KB"  | Add-Content $LogfilePath
}

Upvotes: 2

Views: 178

Answers (1)

mklement0
mklement0

Reputation: 437608

There are two conceivable scenarios (possibly in combination) in which $_.DirectoryName.Replace($Source, $Destination) wouldn't work as intended, and they're not related to Read-Host per se:

  • $Source has a trailing \ (e.g., C:\Dir1\ instead of C:\Dir1)

    • In this case, the .Replace() call malfunctions at least for files immediately located in the target path, because .DirectoryName values do not have a trailing \ (except for files in a root path such as C:\).
  • Perhaps more likely: $Source differs in case from the actual target path; e.g., c:\dir1 vs. C:\Dir1

    • In this case, the .Replace() call fails, because this method is case-sensitive, invariably in Windows PowerShell, by default in PowerShell (Core) 7+.

You could fix those problems by using a substring approach instead of trying to replace the start of the path, which bypasses the case-sensitivity problem:

# Remove the input path from the start and prepend the destination path.
Join-Path $Destination $_.DirectoryName.Substring($Source.Length)

Note that, due to how Join-Path works, the path will be synthesized correctly whether or not $Source has a trailing \, so there is no need to trim one manually first. E.g.,
Join-Path c:\dir2 sub, Join-Path c:\dir2 \sub, and Join-Path c:\dir2\ \sub
all yield the same result: c:\dir2\sub.


Alternative approach: Get-ChildItem has a -Name switch that reports matching files as relative path strings, namely relative to the input directory. This allows for a solution that uses PowerShell cmdlets only, without needing to resort to .NET method calls:

# Note the use of -Name
Get-ChildItem -Name -Path $Source -File -Recurse | ForEach-Object {
    # Prepend the destination path to the parent path of the relative source path.
    $NewDir = Join-Path $Destination (Split-Path -Parent $_)
    # Ensure that the destination dir. exists - note the use of -Force
    $null = New-Item -Force -Path $NewDir -ItemType Directory
    Copy-Item -LiteralPath $_.FullName -Destination $NewDir
    Write-Host '.' -NoNewline
    #added to validate time stamp in log file is working
    Start-Sleep -Seconds 1
    "$([DateTime]::Now) copied - '$($_.name)' from '$($_.DirectoryName)' to '$NewDir' Size: $([System.Math]::Round(($($_.Length)/1KB),2))KB"  | Add-Content $LogfilePath
}

Note that, as in your own attempt, both solutions above rely on $Source and $Destination to contain full paths.

Upvotes: 1

Related Questions