Mike
Mike

Reputation: 143

Cannot find path when using remove-item cmdlet

I wrote out a simple PowerShell script that backs up a directory to C:\ and then deletes any of the backup folders when its age = X days.

For some reason, when I use the Remove-Item cmdlet I'm getting a Remove-Item: Cannot find path 'C:\Windows\system32\ [Sub-Folder name]' because it does not exist error.

Below is the snippet:

    $TargetFolder = "C:\Folder\"
    $Folders = get-childitem -path $TargetFolder
    foreach ($Folder in $Folders)
    {
      remove-item $Folder -recurse -force
    }

Within the $TargetFolder = "C:\Folder\", there are a few sub-folders. Examples: C:\Folder\SubfolderA, C:\Folder\SubfolderB, etc.

When I do a Write-Host for $Folder it lists SubFolderA, SubFolderB, etc, correctly so I'm not exactly sure why I'm getting a Cannot find path error.

Upvotes: 2

Views: 23541

Answers (3)

mklement0
mklement0

Reputation: 437363

tl;dr

To ensure that Remove-Item correctly identifies a directory object as returned by Get-ChildItem (an instance of type [System.IO.DirectoryInfo]):

  • When passing the object as a parameter value (argument), in Windows PowerShell (no longer in PowerShell Core) you must use .FullName for the command to work reliably:

    Remove-Item -LiteralPath $Folder.FullName ... # !! Note the need for .FullName
    

-LiteralPath is not strictly needed, but is the more robust choice, given that Remove-Item $Folder.FullName implicitly binds to the -Path parameter instead, which interprets its argument as a wildcard expression; often this will make no difference, but it can.

  • When using the pipeline, you can pass the objects as-is.

    Get-ChildItem -Directory | Remove-Item ...
    

The surprising need to use .FullName is the result of a design quirk; the resulting behavior and its implications are discussed below; a fix has been proposed in this GitHub issue.


Liturgist's helpful answer and AJK's helfpul answer contain complementary pieces of the solution (Liturgist's answer has since been amended to provide a complete solution):

  • To limit what Get-ChildItem returns to directories (folders), you must use -Directory (PSv3+).

  • To unambiguously identify a filesystem object when passing it to Remove-Object as a parameter that is converted to a string, its full path must be specified.

    • Note: The situational filename-only stringification described below affects only Windows PowerShell; fortunately, the problem has been fixed in PowerShell Core, fortunately.

    • In the case at hand, given that $Folder contains a [System.IO.DirectoryInfo] object as returned by Get-ChildItem, $Folder.FullName is simplest (but constructing the path by prepending $TargetPath works too).

    • In Windows PowerShell, even though $Folder is a [System.IO.DirectoryInfo] object that does contain the full path information, when converted to a string it situationally may only expand to its mere directory name (last path component) - it depends on how the [System.IO.DirectoryInfo] and [System.IO.FileInfo]instances were obtained, namely:
      If Get-ChildItem was called either without a path argument or via a path argument that is a directory, the output objects stringify to their mere file/directory name
      - see this answer for details.
      Simple example: $d = (Get-ChildItem -Directory $HOME)[0]; "$d" yields Contacts, for instance, not C:\Users\jdoe\Contacts.

      • By contrast, if you pass such objects via the pipeline to Remove-Item, PowerShell does use the full path.
    • Important: If you do pass the target folder as a parameter and neglect to specify the full path while not in the same location as the target folder, the name may therefore interpreted as relative to the current location, and you'll either get a Cannot find path error - as you saw - or, even worse, you may end up deleting a different folder by the same name if one happens to be present in your current location (directory).

As stated, you can avoid the full-path problem by piping the folder objects returned by Get-ChildItem to Remove-Item, which also enables a more elegant, single-pipeline solution (PSv3+):

$TargetFolder = "C:\Folder"
$Days = 5
Get-ChildItem -Directory $TargetFolder |  
  Where-Object LastWriteTime -lt (Get-Date).Date.AddDays(-$Days) | 
    Remove-Item -WhatIf -Force -Recurse

Remove the -WhatIf to perform actual removal.

Upvotes: 3

lit
lit

Reputation: 16236

It seems that you want to do this on the basis of the directory LastWriteTime, but you did not mention -Directory on Get-ChildItem.

[cmdletbinding()]
Param()

$TargetFolder = "C:\Users\lit\Documents"
$Folders = Get-ChildItem -Path $TargetFolder -Directory
$Days = 80

foreach ($Folder in $Folders) {
    if ($Folder.LastWriteTime -lt (Get-Date).AddDays(-$Days)) {
        Write-Verbose "Deleting directory $($Folder.FullName)"
        Remove-Item -WhatIf "$($Folder.FullName)" -Recurse -Force
    }
}

Upvotes: 6

AJK
AJK

Reputation: 21

Try executing remove-item on the full path, e.g.

    $TargetFolder = "C:\Folder\"
    $Folders = get-childitem -path $TargetFolder

    foreach ($Folder in $Folders)
    {
      remove-item $TargetFolder$Folder -recurse -force
    }

Upvotes: 2

Related Questions