rm -f equivalent for PowerShell that ignore nonexistent files
Asked Answered
O

3

26

Background

I have a PowerShell script that writes some results into a file.

  • I want to remove the result file automatically at the start of the script with Remove-Item.
  • You can remove the result file manually, so I don't want to show error messages even if the result file doesn't exist.
  • I want to show error messages when the script couldn't remove the result file for another reason, e.g. the file is locked.

You can fulfill all the requirements above with rm -f in Unix-like systems.

Problem

First I have tried Remove-Item -Force, but it couldn't ignore nonexistent files (cf. rm -f ignores nonexistent files).

PS C:\tmp> Remove-Item C:\tmp\foo.txt -Force
Remove-Item : Cannot find path 'C:\tmp\foo.txt' because it does not exist.
At line:1 char:1
+ Remove-Item C:\tmp\foo.txt -Force
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\tmp\foo.txt:String) [Remove-Item], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand

Next, I have tried Remove-Item -ErrorAction Ignore and Remove-Item -ErrorAction SilentlyContinue, but they don't show error messages when they failed to remove the file (cf. rm -f shows an error message like rm: cannot remove 'foo.txt': Operation not permitted in this situation).

PS C:\tmp> $file = [System.IO.File]::Open('C:\tmp\foo.txt',[System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::None)
PS C:\tmp> Remove-Item C:\tmp\foo.txt -ErrorAction Ignore
# I expected it shows an error because it couldn't remove the file because of the lock, but it showed nothing
PS C:\tmp> $file = [System.IO.File]::Open('C:\tmp\foo.txt',[System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::None)
PS C:\tmp> Remove-Item C:\tmp\foo.txt -ErrorAction SilentlyContinue
# I expected it shows an error because it couldn't remove the file because of the lock, but it showed nothing

Question

Is there rm -f equivalent in PowerShell that fulfills all the requirements above?

Onetoone answered 29/7, 2020 at 15:51 Comment(0)
M
30

To me, the simplest solution is:

if (test-path $file) {
  remove-item $file
}

This also occurs to me. $error[0] is always the most recent error.

remove-item $file -erroraction silentlycontinue
if ($error[0] -notmatch 'does not exist') {
  write-error $error[0]  # to standard error
}

I think you can also use try/catch with specific exceptions. Here's an example. I found the exception through tab completion. But the script will stop with other uncaught exceptions. This error doesn't normally stop.

try { remove-item foo -erroraction stop }
catch [System.Management.Automation.ItemNotFoundException] { $null }
'hi'
Milkfish answered 29/7, 2020 at 16:7 Comment(0)
R
2

You cannot do this with that cmdlet by itself. You have to provide additional logic for errors.

'D:\temp\abc.txt', 'D:\Temp\hw.txt', 'D:\Temp\nonexistent.txt.', 'D:\Temp\book1.csv' | 
ForEach{
    try   {Remove-Item   -Path    $PSitem -WhatIf -ErrorAction Stop}
    catch {Write-Warning -Message $PSItem.Exception.Message}
}
# Results
<#
What if: Performing the operation "Remove File" on target "D:\temp\abc.txt".
What if: Performing the operation "Remove File" on target "D:\Temp\hw.txt".
WARNING: Cannot find path 'D:\Temp\nonexistent.txt.' because it does not exist.
What if: Performing the operation "Remove File" on target "D:\Temp\book1.csv".
#>

You should leverage error handling in all your code (interactive and scripts). You can send any screen output to $null, Out-Null, or [void] to prevent it from going to the screen, but still, know that it happened.

For the use case you are asking for, you will need multiple logic (try/catch, if/then) statements.

So, something like this modified wrapper function:

function Remove-ItemNotFileLocked
{
    [cmdletbinding(SupportsShouldProcess)]
    [Alias('rinf')]

    param 
    (
        [parameter(Mandatory = $true)][string]$FullFilePath
    )

    $TargetFile = New-Object System.IO.FileInfo $FullFilePath

    if ((Test-Path -Path $FullFilePath) -eq $false) {return $false}

    try 
    {
        $TargetFileStream = $TargetFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)

        if ($TargetFileStream) 
        {
            $TargetFileStream.Close()
            Remove-Item -Path $FullFilePath
        }
        $false
    } 
    catch 
    {
        $true
    }
}

'D:\temp\abc.txt', 'D:\Documents\Return To Sender.docx','D:\Temp\nonexistent.txt.' | 
ForEach {Remove-ItemNotFileLocked -FullFilePath $PSItem -WhatIf}

# Results
<#
What if: Performing the operation "Remove File" on target "D:\temp\abc.txt".
True
False
#>

Point of note: Text editors like notepad, etc. do not place locks on files.

  • The first message shows a notepad file opened, but can be removed
  • The second shows Word doc opened and locked
  • The third shows a text file, not on the system

If I don't want that screen noise, then... Comment out those $false and $True statements, they are there for debugging and validation efforts.

'D:\temp\abc.txt', 'D:\Documents\Return To Sender.docx','D:\Temp\nonexistent.txt.' | 
ForEach {$null = Remove-ItemNotFileLocked -FullFilePath $PSItem -WhatIf}
# Results
<#
What if: Performing the operation "Remove File" on target "D:\temp\abc.txt".
#>

Of course, remove/comment out the -WhatIf to allow things to happen, and that noise goes away as well.

If you do not want to use a function, then this code block should solve your use case.

# Remove non-Locked file and show screen output
'D:\temp\abc.txt', 'D:\Documents\Return To Sender.docx','D:\Temp\nonexistent.txt.' | 
ForEach{
    try   
    {
        $TargetFile = (New-Object System.IO.FileInfo $PSitem).Open(
                                            [System.IO.FileMode]::Open, 
                                            [System.IO.FileAccess]::ReadWrite, 
                                            [System.IO.FileShare]::None
                      )
        $TargetFile.Close()  
        Remove-Item -Path $PSItem -WhatIf  
    }
    catch [System.Management.Automation.ItemNotFoundException]{$PSItem.Exception.Message}
    catch {$PSItem.Exception.Message}
}

# Results
<#
What if: Performing the operation "Remove File" on target "D:\temp\abc.txt".
Exception calling "Open" with "3" argument(s): "The process cannot access the file 'D:\Documents\Return To Sender.docx' because it is being used by another process."
Exception calling "Open" with "3" argument(s): "Could not find file 'D:\Temp\nonexistent.txt'."
#>

# Remove non-Locked file and silence screen output
'D:\temp\abc.txt', 'D:\Documents\Return To Sender.docx','D:\Temp\nonexistent.txt.' | 
ForEach{
    try   
    {
        $TargetFile = (New-Object System.IO.FileInfo $PSitem).Open(
                                            [System.IO.FileMode]::Open, 
                                            [System.IO.FileAccess]::ReadWrite, 
                                            [System.IO.FileShare]::None
                      )
        $TargetFile.Close()  
        Remove-Item -Path $PSItem -WhatIf 
    }
    catch [System.Management.Automation.ItemNotFoundException]{$null = $PSItem.Exception.Message}
    catch {$null = $PSItem.Exception.Message}
}
# Results
<#
What if: Performing the operation "Remove File" on target "D:\temp\abc.txt".
#>
Resonate answered 29/7, 2020 at 18:19 Comment(0)
T
0

We need to use Test-Path $item to verify if the $item exists or not.

  • When $item does not exist, the Remove-Item $item will ends with error.

So the quiet_rm() function bellow was designed to simulate the linux rm -rf file/folder command.

  • Even the $item does not exists there will be no error message reported.
# Remove a file or folder quietly
# Like linux "rm -rf"
function quiet_rm($item)
{
  if (Test-Path $item) {
    echo "  Removing $item"
    Remove-Item $item  -r -force
  }
}

echo "Clear folder and files"
quiet_rm .\build\
quiet_rm   build.gradle
quiet_rm  .gradle
quiet_rm   gradle
quiet_rm   gradle.properties
Tricorn answered 31/8, 2023 at 4:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.