Steve
Steve

Reputation: 101

Deleting files with special characters

I want to purge log files from a build server, keeping the last few days. I have no trouble with the log files my process creates. This works fine:

$logFolders = Get-ChildItem $buildBase -Recurse -include logs -Attributes D
$logFolders | ForEach-Object { Get-ChildItem $_.fullname -filter *.log | where >{$_.lastwritetime -lt (get-date).adddays(-$purgeAfter) -and -not $_.psiscontainer} |% {remove-item $_.fullname -force} };

However, the logs produced by the build tool look like this:

lrFR_WebHelp - Friday, February 28, 2014 [9.00 PM].mclog

I've tried quoting the file names:

Get-ChildItem $frReports\Reports -filter *.mclog | where {$_.lastwritetime -lt (get-date).adddays(-$purgeAfter) -and -not $_.psiscontainer}| % {write-host ("'" + $frReports + '\Reports'  + '\' + $_ + "'") }

Outputs:

'D:\scratch\Reports\lrFR_WebHelp - Friday, February 28, 2014 [9.00 PM].mclog'

But attempting to delete:

Get-ChildItem $frReports\Reports -filter *.mclog | where {$_.lastwritetime -lt (get-date).adddays(-$purgeAfter) -and -not $_.psiscontainer}| % {remove-item ("'" + $frReports + '\Reports'  + '\' + $_ + "'") }

Outputs:

remove-item : Cannot find drive. A drive with the name ''D' does not exist.
At line:1 char:145
+ ... container}| % {remove-item ("'" + $frReports + '\Reports'  + '\' + $_ + "'") }
+                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: ('D:String) [Remove-Item], DriveNotFoundException
    + FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand

Changing to unix slashes ('/') or escaped slashes ('\\') doesn't change the result

I tried working around the path. Got no errors, but also didn't delete any files.

This:

    $filesToDelete = Get-ChildItem $frReports\Reports -filter *.mclog | where {$_.lastwritetime -lt (get-date).adddays(-$purgeAfter) -and -not $_.psiscontainer}

    Push-Location -path D:\scratch\Reports

    $filesToDelete | ForEach-Object { 
        $fname = ("'" +  $_ + "'");
        write-host $fname;
        remove-item $fname -Force;
        remove-item $_;
}

Outputs this:

'lrFR_WebHelp - Friday, February 28, 2014 [9.00 PM].mclog'
'lrFR_WebHelp - Friday, March 14, 2014 [10.24 PM].mclog'

But doesn't delete the files.

Finally, after snooping around the internet for clues, I tried the fso object

   $fso = New-Object -ComObject Scripting.FileSystemObject
    $buildBase = "D:\scratch";
    $logFolders = Get-ChildItem $buildBase -Recurse -include Reports -Attributes D


    $logFolders | ForEach-Object { Get-ChildItem $fso.GetFile($_.FullName).ShortPath -filter *.MCL | where {$_.lastwritetime -lt (get-date).adddays(-$purgeAfter) -and -not $_.psiscontainer}} #|% {write-host $_.fullname}  };

Outputs:

Exception calling "GetFile" with "1" argument(s): "Exception from HRESULT: 0x800A0035 (CTL_E_FILENOTFOUND)"
At line:1 char:32
+ $logFolders | ForEach-Object { Get-ChildItem $fso.GetFile($_.FullName).ShortPath ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ComMethodTargetInvocation

Didn't have any luck getting more information about $fso.GetFile in PowerShell. Google failed me this time.

Can anyone help? I'd prefer a pure PowerShell solution, but will be grateful for anything that works.

Upvotes: 6

Views: 4934

Answers (1)

Ansgar Wiechers
Ansgar Wiechers

Reputation: 200523

Your problem is caused by the square brackets in the filename. When you pass a string with a path to the (implicit) -Path parameter of Remove-Item, the square brackets are interpreted as wildcard characters. A substring [9.00 PM] would thus match a single character that is either M, P, 9, 0, a dot, or a space. You could use -LiteralPath instead of -Path to avoid this behavior by changing this:

... | % { Remove-Item $_.FullName -Force }

into this:

... | % { Remove-Item -LiteralPath $_.FullName -Force }

However, Remove-Item accepts pipeline input, so you could drop the loop entirely and read the items directly from the pipe:

... | Remove-Item -Force

That way Remove-Item gets the whole FileInfo object from Get-ChildItem and automatically does The Right Thing™.

As a side note: you could further streamline your code by removing the loops, since not only Remove-Item reads from the pipeline, but Get-ChildItem as well. Something like this should work just fine:

$limit = (Get-Date).AddDays(-$purgeAfter)

Get-ChildItem $buildBase -Recurse -Include logs -Attributes D |
  Get-ChildItem -Filter *.mclog |
  Where-Object { $_.LastWriteTime -lt $limit -and -not $_.PSIsContainer } |
  Remove-Item -Force

Upvotes: 6

Related Questions