How to ensure that a logging Cmdlet is called for every exception in a PowerShell script module? - powershell

In a PowerShell (5.1 and later) Script Module, I want to ensure that every Script and System exception which is thrown calls a logging Cmdlet (e.g. Write-Log). I know that I can wrap all code within the module Cmdlets into try/catch blocks, but my preferred solution would be to use trap for all exceptions thrown while executing Cmdlets of the Module
trap { Write-Log -Level Critical -ErrorRecord $_ }
Using the above statement works as intended if I add it to each Cmdlet inside the module, but I would like to only have one trap statement which catches all exceptions thrown by Cmdlets of the Module to not replicate code and also ensure that I do not miss the statement in any Cmdlet. Is this possible?

What I would do is this.
Set multiple Try/Catch block as needed.
Group multiple cmdlet calls under the same block when you can. As you mentionned, we don't want to group everything under 1 giant try/catch block but still, related calls can go together.
Design your in-module functions as Advanced functions, so you can make use of the common parameters, such as... -ErrorAction
Set $PSDefaultParameterValues = #{'*:ErrorAction'='Stop'} so all cmdlets that support -ErrorAction don't fall through the try/catch block.
(You could also manually set -ErrorAction Stop everywhere but since you want this as default, it make sense to do it that way. In any cases You don't want to touch $ErrorActionPreference as this has a global scope amd your users won't like you if you change defaults outside of the module scope.)
You can also redirect the error stream to a file so instead of showing up in the output, it is written to a file.
Here's a self contained example of this:
& {
Write-Warning "hello"
Write-Error "hello"
Write-Output "hi"
} 2>> 'C:\Temp\redirection.log'
See : About_Redirection for more on this.
(Now I am wondering if you can redirect the stream to something else than a file
Additional note
External modules can help with logging too and might provide a more streamlined approach.
I am not familiar with any of them though.
I know PSFramework have some interesting stuff regarding logging.
You can take a look and experiment to see if fit your needs.
Otherwise, you can do a research on PSGallery for logging modules
(this research is far from perfect but some candidates might be interesting)
find-module *logging* | Select Name, Description, PublishedDate,Projecturi | Sort PublishedDate -Descending

Related

powershell: suppress command output during assignment [duplicate]

When my PowerShell script tries, for example, to create a SQL Server object for a server that doesn't exist ("bla" in my case), PowerShell displays lots of PowerShell errors in red.
Since my script checks the value of $? after such calls, and displays and logs errors, I'd rather not have the several lines of PowerShell errors displayed as well.
How can I deactivate those being displayed for my script?
You have a couple of options. The easiest involve using the ErrorAction settings.
-Erroraction is a universal parameter for all cmdlets. If there are special commands you want to ignore you can use -erroraction 'silentlycontinue' which will basically ignore all error messages generated by that command. You can also use the Ignore value (in PowerShell 3+):
Unlike SilentlyContinue, Ignore does not add the error message to the $Error automatic variable.
If you want to ignore all errors in a script, you can use the system variable $ErrorActionPreference and do the same thing: $ErrorActionPreference= 'silentlycontinue'
See about_CommonParameters for more info about -ErrorAction.
See about_preference_variables for more info about $ErrorActionPreference.
Windows PowerShell provides two mechanisms for reporting errors: one mechanism for terminating errors and another mechanism for non-terminating errors.
Internal CmdLets code can call a ThrowTerminatingError method when an error occurs that does not or should not allow the cmdlet to continue to process its input objects. The script writter can them use exception to catch these error.
EX :
try
{
Your database code
}
catch
{
Error reporting/logging
}
Internal CmdLets code can call a WriteError method to report non-terminating errors when the cmdlet can continue processing the input objects. The script writer can then use -ErrorAction option to hide the messages, or use the $ErrorActionPreference to setup the entire script behaviour.
You can also append 2>$null to your command.
Example:
$rec = Resolve-DnsName $fqdn -Server $dns 2>$null
You're way off track here. Silencing errors is almost never a good idea, and manually checking $? explicitly after every single command is enormously cumbersome and easy to forget to do (error prone). Don't set yourself up to easily make a mistake. If you're getting lots and lots of red, that means your script kept going when it should have stopped instead. It can no longer do useful work if most of its commands are failing. Continuing a program when it and the system are in an unknown state will have unknown consequences; you could easily leave the system in a corrupt state.
The correct solution is to stop the algorithm on the first error. This principle is called "fail fast," and PowerShell has a built in mechanism to enable that behavior. It is a setting called the error preference, and setting it to the highest level will make your script (and the child scopes if they don't override it) behave this way:
$ErrorActionPreference = 'Stop'
This will produce a nice, big error message for your consumption and prevent the following commands from executing the first time something goes wrong, without having to check $? every single time you run a command. This makes the code vastly simpler and more reliable. I put it at the top of every single script I ever write, and you almost certainly should as well.
In the rare cases where you can be absolutely certain that allowing the script to continue makes sense, you can use one of two mechanisms:
catch: This is the better and more flexible mechanism. You can wrap a try/catch block around multiple commands, allowing the first error to stop the sequence and jump into the handler where you can log it and then otherwise recover from it or rethrow it to bubble the error up even further. You can also limit the catch to specific errors, meaning that it will only be invoked in specific situations you anticipated rather than any error. (For example, failing to create a file because it already exists warrants a different response than a security failure.)
The common -ErrorAction parameter: This parameter changes the error handling for one single function call, but you cannot limit it to specific types of errors. You should only use this if you can be certain that the script can continue on any error, not just the ones you can anticipate.
In your case, you probably want one big try/catch block around your entire program. Then your process will stop on the first error and the catch block can log it before exiting. This will remove a lot of duplicate code from your program in addition to cleaning up your log file and terminal output and making your program less likely to cause problems.
Do note that this doesn't handle the case when external executables fail (exit code nonzero, conventionally), so you do still need to check $LASTEXITCODE if you invoke any. Despite this limitation, the setting still saves a lot of code and effort.
Additional reliability
You might also want to consider using strict mode:
Set-StrictMode -Version Latest
This prevents PowerShell from silently proceeding when you use a non-existent variable and in other weird situations. (See the -Version parameter for details about what it restricts.)
Combining these two settings makes PowerShell much more of fail-fast language, which makes programming in it vastly easier.
I had a similar problem when trying to resolve host names using [system.net.dns]. If the IP wasn't resolved .Net threw a terminating error.
To prevent the terminating error and still retain control of the output, I created a function using TRAP.
E.G.
Function Get-IP
{PARAM ([string]$HostName="")
PROCESS {TRAP
{"" ;continue}
[system.net.dns]::gethostaddresses($HostName)
}
}
Add -ErrorAction SilentlyContinue to your script and you'll be good to go.
In some cases you can pipe after the command a Out-Null
command | Out-Null
To extend on Mikkel's answer.
If you still want to capture the error, you can use "-ErrorAction stop" combined with a try - catch.
"-ErrorAction silentlycontinue" will ignore the error.
For instance:
try
{
New-Item -Path "/somepath" -Name "somename" -ErrorAction Stop | Out-Null
}
catch
{
echo "You must run this command in an elevated mode."
}
NOTE: There is no "silentlyStop" action, and I believe Mickel's answer refers to the "stop" action. It is likely a typo.
The idea of using a try-catch combined with the "stop" action is to be able to not just dismiss eventual errors but to show something in case of errors.
If you want the powershell errormessage for a cmdlet suppressed, but still want to catch the error, use "-erroraction 'silentlyStop'"

PowerShell query Windows task scheduler for tasks that will run between specific datetimes

I've inherited a server that runs Windows scheduled tasks for several hundred various processes (mostly it kicks off custom PowerShell scripts) - the schedules for these tasks can be as frequently as every 15 minutes, or as infrequently as once per year on a specific date.
How can I query task scheduler using PowerShell (or really any other means also) to determine what if any tasks will be running between a specific date-time range in the future? This is necessary so we can for example schedule and perform maintenance on this server, and others that it interacts with.
I should start out by mentioning this is a deceptively complex topic. There are few reasons for this, including but not limited to:
Scheduled Tasks as objects can be very complex with widely variable schedules, repetition intervals, other trigger types, and a plethora of other configuration points.
The non-GUI, meaning CLI, cmdlet, and/or API tools have changed over the years. And, it's not always easy to string them together to solve the complexities cited in point 1.
WARNING: This report is going to be very tough to write. Before you embark on a challenge like this you should look for any preexisting tools that may inventory or lend other transparency to the Task Scheduler.
Also, it may be better to define the maintenance window as a matter of policy. Then you can configure jobs to avoid the window. At a glance, this strikes me as much easier than writing a difficult program to assist in the cherry-picking of a time slot.
If you choose to move forward here are a few tools that may help:
Schtasks.exe: A surprisingly capable CLI tool that's been around a long time.
The TaskScheduler PowerShell module: A PowerShell module written around scheduling COM objects. It was originally part of the Windows 7 PowerShell pack, but these days you can get it through the PowerShell Gallery. The easiest way is through typical module cmdlets like Find-Module & Install-Module
The newer ScheduledTasks module installed by default on later Windows systems. It's Written around Cim (WMI) and is installed by default in later versions of PowerShell / Windows.
CAUTION: It's easy to confuse the latter 2 tools. Not only are the names are very similar, but there are overlap and similarity between the commands they contain. For example, both modules have a Get-Scheduledtask cmdlet. Two ways to deal with the confusion:
When importing a Module with the Import-Module cmdlet use the -Prefix parameter to add a prefix to the none part of the cmdlet. then use that prefix when calling the cmdlet thereafter.
Call cmdlets with a qualified name like TaskScheduler\GetScheduledTasks
Now to get at the scheduling data. In my experience, the needed details are only exposed through the task's XML definition. Again, you're going to bump up against the complexity that comes with a wide range of scheduling and/or trigger options. You can read about the XML schema here
Here are some examples of how to access the XML data:
schtasks.exe /query /s pyexadm1 /tn <TaskName> /XML
In this case, you'll have to do additional string manipulation to isolate the XML then cast it to [XML] so you can work with it in a typical PowerShell manner. Obviously, there will be challenges to leveraging this tool more broadly. However, it's very handy to know for quick checks and work, especially where the next tool is not immediately available.
Note: if you don't cite the /TN argument all tasks will be returned. While the next method is easier, it's good to know this approach, it will be handy while you are developing.
The next example uses the older TaskScheduler module (#2 above):
$TaskXML = [XML](TaskScheduler\Get-ScheduledTask -ComputerName <ComputerName>-Name <TaskName>).XML
Note: Above assumes no prefix was used. So, you must cite the source module to prevent confusion with the ScheduledTask module.
This example loads the XML text and converts it to an XmlDocument object in a single line. Then you can access data about the task like below:
$TaskXML.Task.Triggers.CalendarTrigger
This may yield output like:
StartBoundary Enabled ScheduleByWeek
------------- ------- --------------
2020-09-14T08:00:00 true ScheduleByWeek
You can run this in mass by leveraging the pipeline, which might look something like below:
$XMLTaskData =
TaskScheduler\Get-ScheduledTask -ComputerName <ComputerName> -Recurse |
ForEach-Object{ [XML]$_.XML }
In the above pipeline example the resulting $XMLTaskData is an array each element of which is a respective XML task definition.
Note: Use of the -Recurse switch parameter. Given the high number of tasks, I wouldn't be surprised if they were organized into subfolders.
Similarly, you can also use the Export-ScheduledTask cmdlet from the ScheduledTasks module:
$TaskXML = [XML](Export-ScheduledTask -CimSession <ComputerName> -TaskName <TaskName>)
And you can leverage the pipeline like this:
$XMLTaskData =
Get-ScheduledTask -CimSession <ComputerName> |
Export-ScheduledTask |
ForEach-Object{ [XML]$_ }
Like the other piped example, this results in an array of XML task definitions.
Note: In this case, there is no -Recurse parameter. You can specifically cite paths though.
With any of these approaches you obviously need some familiarity with working with XML objects in PowerShell, but there are tons of tutorials or other resources for that.
Again, the complexity here is in dealing with many trigger types and scheduling paradigms. On your road to getting a Minimally Viable Program (MVP), you may want to use these techniques to inventory the existing tasks. That can help you prioritize your development process.
A final point; knowing when a task is going to run may be quite different than know when it's running. For example, a task may run a 1:00 PM, but the duration of the job is variable and unaccounted for. That strikes me as very difficult to contend with. You may need another procedure to look for task completion events in the event logs. You may also need to consider execution time limits which can be found in the XML data.
Here is a script to get the next running jobs
Get-ScheduledTask |
Foreach-object { Get-ScheduledTaskInfo $_} |
where-object {!($_.NextRunTime -eq $null)} |
Select-object Taskname, NextRunTime |
Sort-object -property NextRunTime

Get all references to a given PowerShell module

Is there a way to find a list of script files that reference a given module (.psm1)? In other words, get all files that, in the script code, use at least 1 of the cmdlets defined in the module.
Obviously because of PowerShell 3.0 and above, most of my script files don't have an explicit Import-Module MODULE_NAME in the code somewhere, so I can't use that text to search on.
I know I can use Get-ChildItem -Path '...' -Recurse | Select-String 'TextToSearchFor' to search for a particular string inside of files, but that's not the same as searching for any reference to any cmdlet of a module. I could do a search for every single cmdlet in my module, but I was wondering if there is a better way.
Clarification: I'm only looking inside of a controlled environment where I have all the scripts in one file location.
Depending on the scenario, the callstack could be interesting to play around with. In that case you need to modify the functions which you want to find out about to gather information about the callstack at runtime and log it somewhere. Over time you might have enough logs to make some good assumptions.
function yourfunction {
$stack = Get-PSCallStack
if ($stack.Count -gt 1) {
$stack[1] # log this to a file or whatever you need
}
}
This might not work at all in your scenario, but I thought I throw it in there as an option.

Powershell: How can I stop errors from being displayed in a script?

When my PowerShell script tries, for example, to create a SQL Server object for a server that doesn't exist ("bla" in my case), PowerShell displays lots of PowerShell errors in red.
Since my script checks the value of $? after such calls, and displays and logs errors, I'd rather not have the several lines of PowerShell errors displayed as well.
How can I deactivate those being displayed for my script?
You have a couple of options. The easiest involve using the ErrorAction settings.
-Erroraction is a universal parameter for all cmdlets. If there are special commands you want to ignore you can use -erroraction 'silentlycontinue' which will basically ignore all error messages generated by that command. You can also use the Ignore value (in PowerShell 3+):
Unlike SilentlyContinue, Ignore does not add the error message to the $Error automatic variable.
If you want to ignore all errors in a script, you can use the system variable $ErrorActionPreference and do the same thing: $ErrorActionPreference= 'silentlycontinue'
See about_CommonParameters for more info about -ErrorAction.
See about_preference_variables for more info about $ErrorActionPreference.
Windows PowerShell provides two mechanisms for reporting errors: one mechanism for terminating errors and another mechanism for non-terminating errors.
Internal CmdLets code can call a ThrowTerminatingError method when an error occurs that does not or should not allow the cmdlet to continue to process its input objects. The script writter can them use exception to catch these error.
EX :
try
{
Your database code
}
catch
{
Error reporting/logging
}
Internal CmdLets code can call a WriteError method to report non-terminating errors when the cmdlet can continue processing the input objects. The script writer can then use -ErrorAction option to hide the messages, or use the $ErrorActionPreference to setup the entire script behaviour.
You can also append 2>$null to your command.
Example:
$rec = Resolve-DnsName $fqdn -Server $dns 2>$null
You're way off track here. Silencing errors is almost never a good idea, and manually checking $? explicitly after every single command is enormously cumbersome and easy to forget to do (error prone). Don't set yourself up to easily make a mistake. If you're getting lots and lots of red, that means your script kept going when it should have stopped instead. It can no longer do useful work if most of its commands are failing. Continuing a program when it and the system are in an unknown state will have unknown consequences; you could easily leave the system in a corrupt state.
The correct solution is to stop the algorithm on the first error. This principle is called "fail fast," and PowerShell has a built in mechanism to enable that behavior. It is a setting called the error preference, and setting it to the highest level will make your script (and the child scopes if they don't override it) behave this way:
$ErrorActionPreference = 'Stop'
This will produce a nice, big error message for your consumption and prevent the following commands from executing the first time something goes wrong, without having to check $? every single time you run a command. This makes the code vastly simpler and more reliable. I put it at the top of every single script I ever write, and you almost certainly should as well.
In the rare cases where you can be absolutely certain that allowing the script to continue makes sense, you can use one of two mechanisms:
catch: This is the better and more flexible mechanism. You can wrap a try/catch block around multiple commands, allowing the first error to stop the sequence and jump into the handler where you can log it and then otherwise recover from it or rethrow it to bubble the error up even further. You can also limit the catch to specific errors, meaning that it will only be invoked in specific situations you anticipated rather than any error. (For example, failing to create a file because it already exists warrants a different response than a security failure.)
The common -ErrorAction parameter: This parameter changes the error handling for one single function call, but you cannot limit it to specific types of errors. You should only use this if you can be certain that the script can continue on any error, not just the ones you can anticipate.
In your case, you probably want one big try/catch block around your entire program. Then your process will stop on the first error and the catch block can log it before exiting. This will remove a lot of duplicate code from your program in addition to cleaning up your log file and terminal output and making your program less likely to cause problems.
Do note that this doesn't handle the case when external executables fail (exit code nonzero, conventionally), so you do still need to check $LASTEXITCODE if you invoke any. Despite this limitation, the setting still saves a lot of code and effort.
Additional reliability
You might also want to consider using strict mode:
Set-StrictMode -Version Latest
This prevents PowerShell from silently proceeding when you use a non-existent variable and in other weird situations. (See the -Version parameter for details about what it restricts.)
Combining these two settings makes PowerShell much more of fail-fast language, which makes programming in it vastly easier.
I had a similar problem when trying to resolve host names using [system.net.dns]. If the IP wasn't resolved .Net threw a terminating error.
To prevent the terminating error and still retain control of the output, I created a function using TRAP.
E.G.
Function Get-IP
{PARAM ([string]$HostName="")
PROCESS {TRAP
{"" ;continue}
[system.net.dns]::gethostaddresses($HostName)
}
}
Add -ErrorAction SilentlyContinue to your script and you'll be good to go.
In some cases you can pipe after the command a Out-Null
command | Out-Null
To extend on Mikkel's answer.
If you still want to capture the error, you can use "-ErrorAction stop" combined with a try - catch.
"-ErrorAction silentlycontinue" will ignore the error.
For instance:
try
{
New-Item -Path "/somepath" -Name "somename" -ErrorAction Stop | Out-Null
}
catch
{
echo "You must run this command in an elevated mode."
}
NOTE: There is no "silentlyStop" action, and I believe Mickel's answer refers to the "stop" action. It is likely a typo.
The idea of using a try-catch combined with the "stop" action is to be able to not just dismiss eventual errors but to show something in case of errors.
If you want the powershell errormessage for a cmdlet suppressed, but still want to catch the error, use "-erroraction 'silentlyStop'"

Check if an item exists without an error if it doesn't exist

I'd like to use PowerShell to check whether an IIS Web Application exists (or potentially some other kind of item). I can do this with Get-Item, but that reports an error if the item doesn't exist, which is misleading to the user running the script - it looks like something went wrong when actually everything is fine.
How do I do this without an error?
The cmdlet Test-Path is specifically designed for this, it determines whether items of a path exist. It returns a Boolean value and does not generate an error.
The cmdlet Get-Item (and similar) can be used, too, but not directly. One way is already proposed: use -ErrorAction SilentlyContinue. It might be important to know that in fact it still generates an error; it just does not show it. Check the error collection $Error after the command, the error is there.
Just for information
There is a funny way to avoid this error (it also works with some other cmdlets like Get-Process that do not have a Test-Path alternative). Suppose we are about to check existence of an item "MyApp.exe" (or a process "MyProcess"). Then these commands return nothing on missing targets and at the same time they generate no errors:
Get-Item "[M]yApp.exe"
Get-Process "[M]yProcess"
These cmdlets do not generate errors for wildcard targets. And we use these funny wildcards that actually match single items.
Use the command ... get-item blah -ErrorAction SilentlyContinue