2

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.

Braiam
  • 1
  • 11
  • 47
  • 78
Mike
  • 133
  • 2
  • 4
  • 13
  • so why are you doing it like that? why not just Remove-Item -Recurse -Force $targetfolder? – 4c74356b41 Nov 08 '16 at 16:32
  • I didn't want to delete all the contents in $TargetFolder. I only wanted to delete the backup folders that have reached a specific age. Here's the script. @4c74356b41 `code $Now = Get-Date $Days = "5" $TargetFolder = "C:\Folder\" $LastWrite = $Now.AddDays(-$Days) $Folders = get-childitem -path $TargetFolder | Where {$_.psIsContainer -eq $true} | Where {$_.LastWriteTime -le "$LastWrite"} foreach ($Folder in $Folders) { Remove-Item -path $Folder -Recurse -Force } `code` – Mike Nov 08 '16 at 16:49
  • ok, first of all you would want to use get-childitem -directory, to avoid another pipe. also there's no sense in calling $now.adddays, call (Get-Date).AddDays directly. Also, try to use $folder.fullname in the remove-item – 4c74356b41 Nov 08 '16 at 17:06
  • 2
    You should [edit your code into your question](http://stackoverflow.com/posts/40491642/edit). IMO the code sample you have provided only tells half the story of what you want to do and also, you might get better help if you show the whole picture. – default Nov 08 '16 at 17:13

3 Answers3

6

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
    }
}
lit
  • 14,456
  • 10
  • 65
  • 119
  • 1
    Thanks for updating; actually, PowerShell variables generally don't need double-quoting as arguments (unless they're to be _embedded_ in a double-quoted string), even if they contain spaces. Thus, `Remove-Item $Folder.FullName` works just fine. – mklement0 Nov 08 '16 at 19:57
3

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.

mklement0
  • 382,024
  • 64
  • 607
  • 775
2

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
    }
AJK
  • 21
  • 2