Only allow 1 invocation of powershell script to run - powershell

I have a powershell script which starts 2 different Access database applications running. This in a volunteer setting and when the computer is first turned on, it can take a minute or two for the startup to complete. Sometimes the user gets impatient and clicks on the shortcut to the Powershell script more than once, causing the Access databases to start multiple times.
To solve this I thought that the first thing that the script would do would be to create a file. If the create failed due to the file already existing, it would ask the user if they wanted to continue. If yes, run the rest of the script otherwise exit. The problem is that the "catch" after the "try" isn't catching anything. How do I fix this and/or what other solutions do people have?
try {New-Item ($dbDir + "lock_file") -type file | Out-Null} # create lock_file. If it exists, another copy of this is probably running and the catch will run
catch
{
$answer = [System.Windows.Forms.MessageBox]::Show("It appears that the database is already starting. Start again?" , "Start Again" , 4)
if ($answer -eq "NO")
{Exit}
}

Catch only works for terminating errors. Cmdlets that throw errors but continue processing will not be caught by catch (Also called non-terminating errors). One way to change this behavior is to set the -erroraction of a cmdlet which should be common to most of them. In your case I would do this:
try {New-Item ($dbDir + "lock_file") -type file -ErrorAction Stop | Out-Null}
The catch block should trigger now.

Related

How can I change the default PowerShell error for when a command needs elevation?

I have a lot of code in a PowerShell script that are mix of commands that need elevation to run and commands that don't, those that need elevation show errors in PowerShell console like:
"You don't have enough permissions to perform the requested operation"
and
"Requested registry access is not allowed."
is there a way to globally suppress only the kinds of errors that PowerShell shows due to lack of necessary privileges?
I thought about a function that checks for elevation and performs actions based on the result, like this:
https://devblogs.microsoft.com/scripting/use-function-to-determine-elevation-of-powershell-console/
Function Test-IsAdmin
{
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal $identity
$principal.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
and
if(-NOT (Test-IsAdmin))
{ write-host "Skipping Admin command" }
else { $code }
but I don't know how to apply it globally to the whole script, so that commands that don't need elevation run, and those that need elevation show a custom message or skip that part silently.
another thing that can help my situation would be to find out if a PowerShell command needs elevation before actually running it and causing it to show errors in console due to lack of privileges.
It seems that errors stemming from a lack of privileges typically - but not necessarily - involve a System.UnauthorizedAccessException or System.Security.SecurityException .NET exception behind the scenes, whose name is then reflected as part of the .FullyQualifiedErrorId property of the resulting PowerShell error record, which is of type System.Management.Automation.ErrorRecord.
Assuming that this applies to all errors you care about, you can use a (rarely used anymore) trap statement as follows:
trap {
if ($_.FullyQualifiedErrorId -match 'UnauthorizedAccessException|SecurityException') {
Write-Warning "Skipping admin command ($($_.InvocationInfo.Line.Trim()))"
continue # Suppress the original error and continue.
}
# If the error was created with `throw`, emit the error and abort processing.
# SEE CAVEAT BELOW.
elseif ($_.Exception.WasThrownFromThrowStatement) { break }
# Otherwise: emit the error and continue.
}
# ... your script
Caveat:
If your script implicitly raises script-terminating errors - via -ErrorAction Stop or $ErrorActionPreference = 'Stop' - the above solution in effect turns them into statement-terminating errors and continues execution (only explicit script-terminating errors created with a throw statement are recognized as such in the code above, and result in the script getting aborted).
Unfortunately, as of PowerShell 7.2.x, there is no way to generally discover whether a given error is (a) non-terminating, (b) statement-terminating or (c) script-terminating (fatal).
See GitHub issue #4781 for a proposal to add properties to [System.Management.Automation.ErrorRecord] to allow such discovery in the future.

Get Location of Error in PowerShell script (System.Management.Automation)

I have a powershell script running in a c# service (System.Management.Automation). Sometimes seldom I get the following error:
Unable to cast object of type 'System.Double' to type 'System.String'.
Is is somehow possible to include the variable name, line number or anything that could help me find the error location? E.g. code that runs in a powershell terminal shows me the corresponding line.
It's worth thinking through your error handling and error logging approach to avoid issues like this. Consider whether you want your error to be terminating or non-terminating, and whether you want to suppress or log the error.
Much has already been written on SO on detecting and handling script errors, and there is also ample MS documentation on Trap and Try-Catch-Finally, so I don't want to go over that ground.
Depending on where the script runs from and with what credentials, one trick would be to record errors to a logfile you could check when these errors happen:
# Create C:\Temp directory if it doesn't exist
New-Item "C:\Temp" -Force -ItemType Directory | Out-Null
# Simple trap to catch all errors
trap {
# This is a single-line output more suitable for on-screen error messages
Write-Output "Error encountered: $_ $($_.InvocationInfo.PositionMessage.Split("`n")[0])"
# This is a multi-line output to make it easier to find the error location in a file
Add-Content -LiteralPath "C:\Temp\logfiletest.txt" -Value $_,$_.InvocationInfo.PositionMessage
return
}
# Div/0 triggers the trap to test behaviour
1/0
The trap alone (or something like it) should catch the error you're hitting. Though in practice (not knowing how often you run it or how many errors you generate) you'd want to be wary that you don't inadvertently start generating excessively large logfiles on C: - you could avoid this by selecting a lower-risk drive/location and putting a dynamic date into the filename.

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 - Skip files that cannot be accessed

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.

How to confirm completion of previous command in powershell

I have a simple powershell script that gets ran daily to compress and move some log files. How can i test that the command completes successfully before deleting the original log file.
set-location $logpath1
& $arcprg $pram $dest_file $source_file
Move-Item $dest_file $arcdir
If the Move-Item completes ok i want to remove-item $source_file
The completion status of the previous command can be accessed via the special variable $?.
Note that this works best with non-terminating errors (like you would get from Move-Item). Terminating errors are the result of a direct throw or an exception getting thrown in .NET and they alter the flow of your code. Best to use a trap or try/catch statement to observe those type of errors.
One other thing to watch out for WRT $? and console exes is that PowerShell assumes an exit code of 0 means success (i.e. $? is set to $true) and anything else means failure ($? set to $false). Unfortunately not all console exe's observe that exit code convention e.g. there may be multiple success codes and a single failure code (0). For those exes that don't follow the exit code rules, use $LastExitCode as pointed out in the comments to determine success or failure.
Depending on how parnoid you are and what component you are using for archiving, you can check the archive to confirm the file exixts. We use DotNetZip component to zip our archive log files (http://dotnetzip.codeplex.com/).
$zipFileObj = new-object Ionic.Zip.ZipFile($zipName);
[void] $zipFileObj.UpdateFile( "$fileName", "" ) # adds file if doesn't already exist
trap #catch an zip errors and Stop processing
{
write-error "Caught a system exception. Execution stopped"
write-error $("TRAPPED: " + $_.Exception.Message);
exit
}
if ( $zipFileObj.ContainsEntry( $fileName) )
{
remove-item $pathFile # delete file from file-system
}
else
{
# throw error
}