Powershell - Skip files that cannot be accessed - powershell

I'm trying to recursively delete all files and folders inside of a folder with Powershell via:
Remove-Item "C:\Users\user\Desktop\The_folder\*" -Recurse -Force
My problem is that whenever I run it, I get:
Cannot remove item C:\Users\user\Desktop\The_folder\Delete: The process cannot access the file 'C:\Users\user\Desktop\The_folder\Delete' because it is being used by another process.
How do I skip any files I don't have access to because they are in use (i.e. the same way a user would via GUI asking if wanting to skip all files that cannot be accessed)?
I tried the following, but received error:
Remove-Item "C:\Users\mstjean\Desktop\The_folder\*" -Recurse -Force -ErrorAction Continue
Remove-Item : Cannot remove item C:\Users\mstjean\Desktop\The_folder\Delete:
The process cannot access the file 'C:\Users\mstjean\Desktop\The_folder\Delete'
because it is being used by another process.
At line:1 char:1
+ Remove-Item "C:\Users\mstjean\Desktop\The_folder\*" -Recurse -Force -ErrorAction ...
+ CategoryInfo: WriteError: (C:\Users\mstjea...e_folder\Delete:DirectoryInfo) [Remove-Item], IOException
+ FullyQualifiedErrorId: RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand

If you want to suppress the error message and continue executing, you need to use -ErrorAction Ignore or -ErrorAction SilentlyContinue.
See Get-Help about_CommonParameters:
-ErrorAction[:{Continue | Ignore | Inquire | SilentlyContinue | Stop |
Suspend }]
Alias: ea
Determines how the cmdlet responds to a non-terminating error
from the command. This parameter works only when the command generates
a non-terminating error, such as those from the Write-Error cmdlet.
The ErrorAction parameter overrides the value of the
$ErrorActionPreference variable for the current command.
Because the default value of the $ErrorActionPreference variable
is Continue, error messages are displayed and execution continues
unless you use the ErrorAction parameter.
The ErrorAction parameter has no effect on terminating errors (such as
missing data, parameters that are not valid, or insufficient
permissions) that prevent a command from completing successfully.
Valid values:
Continue. Displays the error message and continues executing
the command. "Continue" is the default value.
Ignore. Suppresses the error message and continues
executing the command. Unlike SilentlyContinue, Ignore
does not add the error message to the $Error automatic
variable. The Ignore value is introduced in Windows
PowerShell 3.0.
Inquire. Displays the error message and prompts you for
confirmation before continuing execution. This value is rarely
used.
SilentlyContinue. Suppresses the error message and continues
executing the command.
Stop. Displays the error message and stops executing the
command.
Suspend. This value is only available in Windows PowerShell workflows.
When a workflow runs into terminating error, this action preference
automatically suspends the job to allow for further investigation. After
investigation, the workflow can be resumed.
If you're having terminating errors that -ErrorAction is not trapping, then you have to trap them yourself with a try / catch.
Here's a naive example:
Get-ChildItem "C:\Users\user\Desktop\The_folder\*" -Recurse -Force `
| Sort-Object -Property FullName -Descending `
| ForEach-Object {
try {
Remove-Item -Path $_.FullName -Force -ErrorAction Stop;
}
catch { }
}
Here, I'm using -ErrorAction Stop to turn all non-terminating errors into terminating errors. Try will trap any terminating error. The catch block is empty, however, so you're trapping everything and then not doing any error handling. The script will continue silently. This is basically equivalent to VBScript's On Error Resume Next. You have to iterate through the files, however, otherwise Remove-Item will stop at the first error.
Note that I have a Sort-Object in there. That's so the items coming through the pipeline are in reverse order. That way, files and subdirectories will be deleted before the directories that contain them. I'm not 100% sure if that method is perfect, but I think it should work. The alternative is really messy.
Obviously, there's no way to tell from output when an error occurs or what wasn't deleted. We're trapping all errors and then throwing them away. Usually an empty catch block is a really bad idea, so use this method with caution!
You'll note that I'm not actually testing to see if the file is opened or locked. That's because it's kind of a waste of time. We don't really care why the file can't be deleted, just that it can't and when it can't we skip it. It's easier (and faster) to try to delete the file and trap the failure than it is to check if it's locked, use a conditional to decide to delete the file, and then delete the file or continue.

Related

How to save powershell hyper-v errors to file

I am making a script to turn virtual machines on and off in hyper-v.
Sometimes the Stop-VM command fails and I need to save the bug or reflect it in some way in a log file
I tried putting the command in a trycath but it didn't work.
Command:
Stop-VM $VMapagar
Sometimes the command gives me this error and does not turn off the machine
Stop-VM: Could not stop.
I would like to be able to reflect the failure in some way in a log.txt
Thanks!
Use Try..Catch to trap the error by telling PS to treat it as a terminating error, then process it as you require:
# Rest of your script
Try {
# Run your command, but tell PS to stop if it find an error
# You can explore the effects of the other possible values for -ErrorAction in PS documentation.
Stop-VM $VMpagar -ErrorAction Stop
# If it's got this far, then there can't have been an error so write a success message to console
Write-Host "OK"
}
Catch {
# This code will process if there was an error in the "Try" block
# By default, within the "Catch" block, the "$_" variable contains the error message
Write-Host "Error: $_"
# Write the error to a log file - "`n" tells PS to write a newline before the subsequent text
Add-Content -Path 'c:\temp\log.txt' -Value "`n$_"
# You could stop the script here using "Throw" or "Exit" commands if you want the whole script to stop on ANY error
}
# Your script will continue from this point if you haven't stopped it
Scepticalist's helpful answer shows how to capture a terminating error, by using the common -ErrorAction (-ea) parameter with value 'Stop' in order to promote non-terminating errors (the most common kind) to terminating ones, which allows them to be trapped with a try/ catch / finally statement.
Note that this approach limits you to capturing the first non-terminating error (whereas a single cmdlet call may emit multiple ones), because it - thanks to -ErrorAction Stop - then instantly terminates the statement and transfers control the catch block (where the automatic $_ variable reflects the triggering error in the form of an [ErrorRecord] instance).
Also note that execution continues after a catch block by default - unless you explicitly use throw to re-throw the terminating error (or use a statement such as exit to exit the script).
To capture - potentially multiple - non-terminating errors you have two options:
Redirect them directly to a file, using the redirection operator > with the number of the error stream, 2:
Stop-Vm $vms 2>errs.txt
This sends any errors quietly to file errs.txt; that is, you won't see them in the console. If no errors occur, an empty file is created.
Note: This technique is the only option for directly redirecting an external program's errors (stderr output); however, using redirection 2>&1 you can capture success output (stdout) and errors (stderr) combined, and split them by their source stream later - see the bottom section of this answer.
Use the common -ErrorVariable (-ev) parameter to collect any non-terminating errors in a variable - note that the target variable must be specified without the $:
Stop-Vm $vms -ErrorVariable errs
By default, the errors are still output as well and therefore print to the console (host) by default, but you can add -ErrorAction SilentlyContinue to prevent that. Caveat: Do not use -ErrorAction Ignore, as that will categorically suppress errors and prevent their collection.
You can then inspect the $errs array (list), which is empty if no errors occurred and otherwise contains one or more [ErrorRecord] instances, and send the collected errors to a file on demand; e.g.:
if ($errs) { $errs > errs.txt }
See also:
This answer for information about PowerShell's two fundamental error types.
GitHub docs issue #1583 for a comprehensive overview of PowerShell's surprisingly complex error handling.

Powershell: cannot hide 'Access is denied' error on 'Remove-Item'

I would like to keep a Remove-Item instruction quiet, exception or not. I'm running below command in a script to delete a certificate:
Remove-Item $store\$thumbprint
If I run the script as local Admin, fine... it keeps quiet and the file is deleted.
If however I run it as unpriviledged user, I get an 'Access in denied' error as expected, but I would like to keep this quiet in any case.
I've tried the following:
$output = (Remove-Item $store\$thumbprint)
# or...
try{Remove-Item $store\$thumbprint} catch{}
# or...
Remove-Item $store\$thumbprint -ErrorAction SilentlyContinue
But I always get the error/exception
displayed on the console.
By default, a non-terminating error is generated by Remove-Item and it adds an error to the $Error variable without throwing an exception. To see what Windows PowerShell will do when a non-terminating error arises, look at the value of the $ErrorActionPreference variable (its default value is Continue).
The Access to the path '…' is denied is an example of such a non-terminating error so you can use ErrorAction parameter which overrides the value of the $ErrorActionPreference variable for the current command:
Remove-Item $store\$thumbprint -ErrorAction SilentlyContinue
On the other side, $ErrorActionPreference and the ErrorAction parameter don't affect how PowerShell responds to terminating errors that stop cmdlet processing. So if we are not sure whether an error is terminating or not then it's safe to handle any error the Try-Catch-Finally blocks using -ErrorAction Stop as follows:
try {
Remove-Item $store\$thumbprint -ErrorAction Stop
} catch {
### A Catch block can include commands for tracking the error
### or for recovering the expected flow of the script
}

How to catch error from Remove-Item and emit a warning instead?

In a post-deployment script used in a continuous integration pipeline (Azure DevOps), I'm removing old files.
Basically, it's a PowerShell script that removes every release folder but the current one in the deployment directory.
Sometimes, the Remove-Item fails for some reason (old file still opened by someone one the deplyoment machine, for instance)
It's not a big deal. I don't want an error saying my whole deployment failed because of this. However, I want a warning, so I'm aware that it happened.
For instance (MCVE):
Remove-Item INEXISTENT_FILE
Problem : it causes an error.
Attempt 1 :
Remove-Item INEXISTENT_FILE -ErrorAction SilentlyContinue
Problem : It removes the Error completely, that's not what I want (I want a warning)
Attempt 2 : I tried to use ErrorVariable as recommended here : https://devblogs.microsoft.com/powershell/erroraction-and-errorvariable/
Remove-Item INEXISTENT_FILE -ErrorAction SilentlyContinue -ErrorVariable $removeItemError
if ($removeItemError) {
Write-Warning "Warning, something failed!"
}
Problem : it doesn't work, it doesn't show the if part. If I remove "SilentlyContinue" error action, it just emits an error, and in any case never goes into the if part.
Attempt 3 : I tried to use also Try Catch block as proposed here : PowerShell -ErrorAction SilentlyContinue Does not work with Get-ADUser
Try {
Remove-Item INEXISTENT_FILE
}
Catch {
Write-Warning "Warning, something failed!"
}
Problem : it never goes into the catch block either (!?)
Anyone has another option to show a warning instead of an error if Remove-Item fails ?
The error produced by Remove-Item is considered 'non-terminating', which means that it is ignored by 'try/catch'. To force it to become 'visible' to 'try/catch' use the ErrorAction parameter:
Remove-Item INEXISTENT_FILE -ErrorAction Stop
Alternatively, you can change this at the script level (i.e. for all subsequent commands) like this:
$ErrorActionPreference = 'Stop'
The error message can be retrieved using $_.Exception.Message or $error[0]

How to get the error code when there is error in powershell?

My snippet is something like this:
$msg=Remove-Item -Recurse -Force C:\users\bkp 2>&1
if ($LASTEXITCODE -eq 1)
{
"Encountered error during Deleting the Folder. Error Message is $msg. Please check." >> $LogFile
exit
}
The folder C:\users\bkp does not exist. Even though $msg gives me the error message $LASTEXITCODE is still 0. How do I capture as a flag?
You can use the $? automatic variable to determine the result of the last command. If you need access to the actual error, you can use the $Error automatic variable. The first item in the array is the last error thrown:
Remove-Item -Recurse -Force C:\users\bkp 2>&1
if( -not $? )
{
$msg = $Error[0].Exception.Message
"Encountered error during Deleting the Folder. Error Message is $msg. Please check." >> $LogFile
exit
}
$LASTEXITCODE is strictly for command line programs to return their status. Cmdlets that are built into PS, such as Remove-item return their errors in up to 3 ways. For warnings, they write messages (or other .NET objects) to the "warning stream". In PSv3 there is a straightforward way to redirect that stream to a file: cmdlet blah blah blah 3>warning.out. The second is via the error stream. That stream can be redirected as well ... 2>error.out, or more typically errors are caught with try/catch or trap, or written to a variable with the -ErrorVariable parameter (see help about_commonparameters). The third way is for errors to be "thrown". Unless caught (try/catch or trap), a thrown error will cause the script to terminate. Thrown errors generally are subclasses of the .NET class system.Management.Automation.ErrorRecord. An ErrorRecord provides a lot more information about an error than a return code.
If remove-item fails due to a file not found error, it writes a System.Management.Automation.ItemNotFoundException to the error stream. Using a try/catch you can filter for that specific error or other specific errors from remove-item. If you are just typing in PS commands from the command line you can enter $error[0]|select-object * to get a lot of info on the last error.
You could do this:
try {
Remove-Item -Recurse -Force C:\users\bkp 2>&1
} catch {
# oops remove-item failed. Write warning then quit
# replace the following with what you want to do
write-warning "Remove-item encounter error: $_"
return # script failed
}

Capturing Non-Standard Powershell CmdLet Output for Flow Control

Currently trying to build a script utilizing cmdlets from the MS released "Team Foundation Server Power Tools" package.
I'm attempting to flow command logic from the success or failure of the "Update-TfsWorkspace" cmdlet however I can't seem get a return code out of the call nor can I capture the output using Out-String. I'm using Powershell v1.
update-tfsworkspace "C:\doesnotexist\" -recurse -version T
Yields a message of "Unable to determine the workspace." which is the error I'm trying to catch.
$ret = update-tfsworkspace "C:\doesnotexist\" -recurse -version T
Is expected to give me a $true/$false indicating success/fail but doesn't work.
update-tfsworkspace "C:\doesnotexist\" -recurse -version T | Out-Null
Is expected to prevent the cmdlet from writing the message but doesn't work.
trap{echo "fail"}
update-tfsworkspace $workspace_path -recurse -version T
Is expected to catch an error and write "fail" but doesn't work.
$msg = update-tfsworkspace $workspace_path -recurse -version T | Out-String
Is expected to populate a $msg variable with the host output but doesn't work.
I'm totally out of ideas here. Help would be appreciated!
Little hacky, but since I don't have TFS to try to figure something else out, see if this helps.
I would say that this cmdlet wasn't written correctly. First, since it didn't succeed it should have emitted an error object which would have caused $? to return false which you could have checked or trapped. Second, you can't suppress the error message using -ea 0. It looks like this snapin is using the Host api to write an error string to the host console. That's a spew!! For now, you could do what EBGreen suggests:
$msg = powershell.exe -nologo update-tfsworkspace "C:\doesnotexist\" -recurse -version T 2>&1
Just watch out for all the text your profile script spits out when a new instance of PowerShell starts up.
Your problem here is that the cmdlet is writing an error (Non-Terminating Error), but not throwing an exception (Terminating Error). You can make it throw an exception by adding the ErrorAction parameter:
trap{echo "fail"}
update-tfsworkspace $workspace_path -recurse -version T -ErrorAction "Stop"
This will cause the cmdlet to make all errors terminating (throwing an exception if it writes to the error stream).