I am starting in the path:


This path has subfolders:



and files:



Ultimately, I want the files to incorporate the folders under the starting folder in the their file anmes. Namely:

D:\starting\path\sub\01\fileA (sub-01).txt

D:\starting\path\sub\02\fileB (sub-02).txt

I have tried the following one-line script with no success.

D:\starting\path> $startPath=(Get-Location); $startPath=($startPath -replace "\\","\\"); $startPath=($startPath+"\\"); Get-ChildItem -file -recurse | foreach-object {$currentPath=($_.Directory.ToString() -replace "\\","\\"); $append=((($currentPath -replace "${startPath}","") -replace "\\\\","-")); $append=(" ("+$append+")"); $new=($_.directory.ToString()+"\"+$_.BaseName+$append+$_.extension); echo $_.fullname $new}

Note: The one-liner above has been updated from original to replace $append=((($_.directory -replace "${startPath}","") -replace "\\","-")); with $currentPath=($_.Directory.ToString() -replace "\\","\\"); $append=((($currentPath -replace "${startPath}","") -replace "\\\\","-"));. The $_.directory -replace ... in the original failed to convert it to a string and then change single backslashes to double backslashes. This would have created problems in and of itself but did not solve the original problem.

The script returns the following output:

D:\starting\path\sub\01\fileA (D:-starting-path-sub-01).txt

D:\starting\path\sub\02\fileB (D:-starting-path-sub-02).txt

The problem clearly lies somewhere in the first -replace but I do not know why. It clearly has to do something with the path D:\starting\path being inside a variable rather than being manually entered as a string. What is special about the path being stored in a string to cause this? I have also tried the same script except I removed $startPath=($startPath -replace "\\","\\");

Your guidance is appreciated.

note: The echo $_.fullname $new will ultimately be mv $_ $new but I use echo during testing.


Script in block form (minor update and one fix made ... but doesn't solve original problem)

# Assume
# D:\start\path
$startPath=(Get-Location) # D:\start\path
$startPath=($startPath -replace "\\","\\") # D:\\start\\path
$startPath=($startPath+"\\") # D:\\start\\path\\
Get-ChildItem -recurse | foreach-object {
    $currentPath=(($_.directory.ToString()) -replace "\\","\\")
    $append=((($currentPath -replace "${startPath}","") -replace "\\\\","-")) 
    # first replace D:\\start\\path\\sub\\01 --> sub\\01 (THIS ISN'T HAPPENING)
    # second replace sub\\01 --> sub-01
    $append=(" ("+$append+")")
    echo $_.fullname $new

The $currentPath and $startPath variables in $append=((($currentPath -replace "${startPath}","") -replace "\\\\","-")) are returning the expected strings. That is, $currentPath: D:\\start\\path\\sub\\01 and $startPath: D:\\start\\path\\. Why the $startPath isn't being replace with "" in $currentPath is unclear.

  • Try doing this as not-a-one-liner and insert some Write-Host cmdlets at various stages to see what the intermediate results are. Then, edit your question to include what you want to happen, and what actually does happen. Feb 11, 2021 at 23:38
  • In the original post, what I wanted output was already detailed. I do not want to write it as anything but a one-liner script. I need to understand why this is happening as a one-liner as I intend to use similar one-liners in the future. I have updated the post to provide an example of what output is being produced.
    – Brian
    Feb 12, 2021 at 0:54
  • 1
    But that is not a one-Liner at all. It's a full script using semi-colons to allow code all on one line. That being said, in your troubleshooting effort, doing this in real PS Editor ISE/VSCode, using breakpoints to stop at given points and then inspecting, is what I believe Jeff Zeitlin is suggesting. He is not just randomly suggesting that you not put this all on one line. If you are doing this at the interactive consolehost session, then that makes sense, but doing this in a true saved/production script is just industry bad practice. Yet, we all make a choice & this has to be split to check
    – postanote
    Feb 12, 2021 at 2:53
  • Split, meaning anyone trying to help you, has to break this long string into a real script to troubleshoot. To truly see what is actually going on. Just my quick look at this, after doing the aforementioned, points to several things, I'd call into question. Yet, again, if you again, break into a troubleshooting format, you could see what I am driving at. Now, once all the troubleshooting is done, you can concatenate back all code on one line.
    – postanote
    Feb 12, 2021 at 2:57
  • Agreed. Based on what the OP says they are after, the first 3 lines are not warranted at all. A simple Read-Host to a variable and paring that in a loop would be more prudent.
    – postanote
    Feb 12, 2021 at 3:06

7 Answers 7


Use conditional logic for better control and avoid null command parameter errors. Don't use -replace and just join together the last two folders with split using the index values [-1..-2].

Join the two folders back together with the hyphen character -. Use the subexpression operator to create the $append variable as a string and wrap it with a parenthesis.

Now concatenate that to the file name before the extension at the end of the base file name.


$startPath = (Get-Location);
$startPath = "$($startPath.Split("\") -join "\\")\\";
Get-ChildItem $startPath -recurse | % { Process {
    If( $_.directory ) { $currentPath = $(($_.directory.ToString()).Replace("\\","\\"))};
    $append = "($($currentPath.Split("\")[-1..-2] -join "-"))";
    $new = If( $_.directory ) { ( "$($_.directory.ToString())\$($_.BaseName)$append$($_.extension)" ) };
    echo $_.fullname $new

Supporting Resources

  • I also threw in a couple other less variables to set too. This should get you what you see @Brian. Feb 12, 2021 at 5:08

Continuing from my comment.

For troubleshooting in the ISE or VSCode:


$startPath=(Get-Location); $startPath=($startPath -replace "\\","\\"); $startPath=($startPath+"\\"); Get-ChildItem -recurse | foreach-object {$append=((($_.directory -replace "${startPath}","") -replace "\\","-")); $append=(" ("+$append+")"); $new=($_.directory.ToString()+"\"+$_.BaseName+$append+$_.extension); echo $_.fullname $new}

...becomes this...

$startPath = 'D:\Temp'
$startPath = ($startPath -replace "\\","\\")
$startPath = ($startPath+"\\")

Get-ChildItem -recurse | 
foreach-object {
$append = (
        ($_.directory -replace "${startPath}","") -replace 
$append = (" ("+$append+")")
$new    = ($_.directory.ToString()+"\"+$_.BaseName+$append+$_.extension)

echo $_.fullname $new}

... immediately all the bad stuff stands out and then after you step thru it, is this what you were after ( a true one-liner):

Get-ChildItem -Path (Read-Host -Prompt 'Enter a starting path') -File -Recurse | 
Select-Object -First 9 | 
foreach-object {"$($PSItem.BaseName) - $($PSItem.Directory)$($PSItem.Extension)"}
# Results
abc - Copy - Copy - D:\temp.bat
abc - Copy - D:\temp.bat
abc - D:\temp.bat
about_Redirection  Microsoft Docs - D:\temp.url
aes - D:\temp.key
available13.html.2019-03-26_081523 - D:\temp.bak

Point of note:

Programatically speaking, special characters, spaces, in the directory, filenames, variable names, property names, et all, is a very bad idea long term.

For stuff you inherit or that is already in place, we all have to deal with that, but for stuff, you control and or can manipulate, well, you know.


I have finally guessed what you want, and I have written a script from scratch, I haven't tested it because I can't use a computer right now, but it should do what you want:

$startdir=Read-Host "Please input starting path"
$files=(Get-ChildItem -Path $startdir -File -Force -Recurse).FullName
foreach ($file in $files) {
    $directories=(Split-Path -Path $file -Parent).split("\")
    $parent=$directories | Select-Object -Last 1
    $grandparent=$directories | Select-Object -Index $($directories.count - 2)
    $extension=$file.split(".") | Select-Object -Last 1
    Rename-Item -Path $file -NewName $($name+" ("+$grandparent+"-"+$parent+")."+$extension)

Save as a .ps1 file, and it should give you what you want.

  • 1
    @DrinkSomePimpJuiceIT Nope, the -File parameter specifies the result should only list the files and not directories, the -Force parameter makes Get-ChildItem list hidden files. Feb 12, 2021 at 4:24

You already have a lot of working answers, but I want to join the fun :-)

$Path = Get-Location | Select-Object -ExpandProperty Path
Get-ChildItem $Path -File -Recurse | ForEach-Object {
    $Rename = $_.Directory -replace ([regex]::Escape($Path) + "\\"),'' -replace '\\','-'
    $NewName = "{0} ({1}){2}" -f $_.BaseName, $Rename, $_.Extension
    Rename-Item $_.FullName -NewName $NewName -WhatIf

$Path can also be a string like $Path = "D:\start\path"

What's good about this is that through -replace ([regex]::Escape($Path) + "\\"),'' we basically delete the "starting path" from the string, and then we can simply replace all \ with ' and we're done. That's the whole magic and will work no matter how many subdirectories you have and how deep they go.

You can run this script for testing as it is and it won't rename anything because of the -WhatIf. It will only tell you what it does. But if you found that it does everything as it should, you can replace -WhatIf with -Force


I can finally read OP's code, and I recognized why it isn't working.

First, the second, third and fifth lines of code are unnecessary and should be removed

Second, the -replace operator uses regex matches, so we need to use double backslashes for literal backslashes, however here we are not trying to do regex matches(there is only one possible matching value for the starting directory), so it is better to use .replace() method instead.

Third, the Get-Location cmdlet requires you to Set-Location first, better use Read-Host so you can process any location without having to change directory first.

The fixed code:

$startPath=Read-Host "Please input start path" # Input D:\Start\Path\
Get-ChildItem -path $startPath -file -force -recurse | foreach-object {
    $append=" ("+$_.directory.ToString().replace($startPath).replace("\","-")+")"
    echo $_.fullname $new

Try the fixed code, I haven't tested it because I am unable to do so at the moment, so please test it and tell me the results so I can further improve it, however I think it should be working.

Save as a .ps1 file(e.g. script.ps1) and cd (or sl) to its location and use something like .\script.ps1 to invoke it.

Inspired by this answer https://superuser.com/a/1625400/1250181, I further improved my code:

$startPath=Read-Host "Please input start path" # Input D:\Start\Path\
Get-ChildItem -path $startPath -file -force -recurse | foreach-object {
    $append=" ("+$($_.directory.ToString().Split("\")[-1..-2] -join "-")+")"
    echo $_.fullname $new

As others have noted, you don't need to be using Search & replace & worrying about escaping backslashes. Here's my take on what you asked for. All the logic is easily contained within the -NewName ScriptBlock as the name is constructed from elements of the file's fully-qualified path.

(Get-ChildItem -File -Recurse) | Rename-Item -NewName {
    $Ancestors = $_.DirectoryName.Split('\')
    '{0} ({1}-{2}){3}'-f $_.BaseName, $Ancestors[-2], $Ancestors[-1], $_.Extension


-f (Format operator)

  • Note: While several other answers use the -Force parameter with Get-ChildItem, I wouldn't in this case. Doing so would rename desktop.inifiles, breaking things like custom folder icons.


I use -replace '\\', '/' to achieve this goal.

 Liu.D.H  $CIPHEY_CORE_PYTHON=(Split-Path -Parent (Get-Command python.exe).Path)
 Liu.D.H  $CIPHEY_CORE_PYTHON=($CIPHEY_CORE_PYTHON -replace '\\', '/')
 Liu.D.H  echo ((Split-Path -Parent (Get-Command python.exe).Path) -replace '\\', '/')
 Liu.D.H   ~   

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .