How to stop power shell script on error? - bash

I have bellow script
$ErrorActionPreference = "Stop";
while($true) {
try {
Write-Host "Step 1";
Dir C:\arts #Error
Write-Host "Step 2";
exit 0
break;
}
catch {
"Error in " + $_.InvocationInfo.ScriptName + " at line: " + $_.InvocationInfo.ScriptLineNumber + ", offset: " + $_.InvocationInfo.OffsetInLine + ".";
$Error
exit 1
break;
}
}
It stops on Dir C:\arts line and that is good for me. As I understood it happens cos I have line $ErrorActionPreference = "Stop"; at the beginning.
I also have some docker params
Param(
[Parameter(Mandatory=$True,ParameterSetName="Compose")]
[switch]$Compose,
[Parameter(Mandatory=$True,ParameterSetName="ComposeForDebug")]
[switch]$ComposeForDebug,
[Parameter(Mandatory=$True,ParameterSetName="StartDebugging")]
[switch]$StartDebugging,
[Parameter(Mandatory=$True,ParameterSetName="Build")]
[switch]$Build,
[Parameter(Mandatory=$True,ParameterSetName="Clean")]
[switch]$Clean,
[parameter(ParameterSetName="Compose")]
[Parameter(ParameterSetName="ComposeForDebug")]
[parameter(ParameterSetName="Build")]
[parameter(ParameterSetName="Clean")]
[ValidateNotNullOrEmpty()]
[String]$Environment = "Debug"
)
If I put $ErrorActionPreference = "Stop" line before docker params I will have error Cannot convert value "System.String" to type "System.Management.Automation.SwitchParameter". Boolean parameters accept only Boolean values and numbers, such as $True, $False, 1 or 0.
In case if I put $ErrorActionPreference = "Stop"; line after docker params, script is continued to run and that is not that I want.
I do not know what I need to do here, so I will be grateful for any help

$ErrorActionPreference doesn't work with command line utilities like docker as they don't throw exceptions in PowerShell. You would have to use returncode/errorlevel or parse the output to handle those type of errors. Useful automatic variables:
$?
Contains the execution status of the last operation. It contains
TRUE if the last operation succeeded and FALSE if it failed.
$LastExitCode
Contains the exit code of the last Windows-based program that was run. Same as %errorlevel% in cmd.
If you detect an error, you can throw an exception to stop the script or use something like exit to stop the script. Example:
function Test-Error {
$ErrorActionPreference = "Stop"
Write-Host Before
ping -n 1 123.123.123.123
#If last command was not successfull.
#You can also have checked $lastexitcode, output etc.
if($? -eq $false) {
#Throw terminating error
#throw "Error"
#Or since we've chosen to stop on non-terminating errors, we could use:
Write-Error -ErrorId $LASTEXITCODE -Message "Ping failed"
}
Write-Host After
}
Test-Error
Output:
Before
Pinging 123.123.123.123 with 32 bytes of data:
Request timed out.
Ping statistics for 123.123.123.123:
Packets: Sent = 1, Received = 0, Lost = 1 (100% loss),
Test-Error : Ping failed
At line:22 char:1
+ Test-Error
+ ~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : 1,Test-Error
If you're creating a advanced function, you could set the default ErrorAction for the scope of the cmdlet like this:
function Test-Error {
[CmdLetBinding()]
param(
$Name = "World"
)
#If -ErrorAction is not specified by the user, use Stop for the scope of the function
if(-not $MyInvocation.BoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" }
"Hello $Name ! My ErrorAction is: $ErrorActionPreference"
}
PS > $ErrorActionPreference
Continue
PS > Test-Error -ErrorAction Ignore
Hello World ! My ErrorAction is: Ignore
PS > Test-Error
Hello World ! My ErrorAction is: Stop

Related

Powershell - Extract timestamp

i am completely new to Powershell and I am trying to create a script to extract the timestamp for the string found in a log file. There are log files everyday and it should select the latest file. It then looks for a string in that file. Now, where it finds the string it should also return a timestamp where the string is located. I am only able to complete the first part and struggling with the 2nd bit. My script is in no way optimal but it is a start. Any help is appreciated.
Set-Location -Path "O:\xyz\0502251\logs"
$latest = (Get-ChildItem -Path $dir -Filter 'async_SPARKLE_ONITE_*.log' | Sort-Object CreationDate -Descending | Select-Object -last 1).fullname
$pattern = "Successfulyl finished running psp_dba_maint_4"
$search = (Get-Content $latest | Select-String -Pattern "psp_dba_maint_4" | Select-Object -last 1)
if($search)
{
"The overnight completed at:"
}
else
{
"Do something"
}
This looks for the latest file and extracts the string. Now Just want to know at what time this log was generated.
The log is:
Connection 1
Server message: 2022-10-15 23:56:07:020
Message number: 0, Severity 10, State 1, Line 126
Procedure 'psp_dba_locklogin'
Message String: -----> login SPRK_SVC_IRISS logged in at 23:55:45 running SRI.exe:AWAITING COMMAND on rptSPARKLE from host da-pvrep02 is still active
Connection 1
Server message: 2022-10-15 23:56:07:020
Message number: 0, Severity 10, State 1, Line 126
Procedure 'psp_dba_locklogin'
Message String: -----> login SPRK_SVC_IRISS logged in at 23:55:45 running SRI.exe:AWAITING COMMAND on rptSPARKLE from host da-pvrep05 is still active
Connection 1
Server message: 2022-10-15 23:56:07:020
Message number: 0, Severity 10, State 1, Line 126
Procedure 'psp_dba_locklogin'
Message String: -----> login SPRK_SVC_IRISS logged in at 23:55:39 running SRI.exe:SELECT on rptSPARKLE from host da-pvrep02 is still active
Connection 1
Server message: 2022-10-15 23:56:07:020
Message number: 0, Severity 10, State 1, Line 126
Procedure 'psp_dba_locklogin'
Message String: -----> login SPARKLE_CAI logged in at 23:55:37 running SPARKLE ESB (pooled):AWAITING COMMAND on A_uatSPK from host DA-UVESB01 is still active
Connection 1
Server message: 2022-10-15 23:56:07:020
Message number: 0, Severity 10, State 1, Line 355
Procedure 'psp_dba_dbrefresh'
Message String: Setting PrivShield on SPRKSZCBS OUT OF maintenance mode
Connection 1
Server message: 2022-10-15 23:56:07:022
Message number: 0, Severity 10, State 1, Line 362
Procedure 'psp_dba_dbrefresh'
Message String:
Connection 1
Server message: 2022-10-15 23:56:07:022
Message number: 0, Severity 10, State 1, Line 364
Procedure 'psp_dba_dbrefresh'
Message String: - Refresh 2 complete
Connection 1
Server message: 2022-10-15 23:56:07:028
Message number: 0, Severity 10, State 1, Line 42
Procedure 'psp_dba_maint_4'
Message String: Successfulyl finished running psp_dba_maint_4
This has different values in files generated everyday. So it should look for the latest file and return the result as The overnight completed at: 23:56.
Here is a variant using the switch statement to efficiently process the file line-by-line and test each line to match given RegEx patterns:
$found = switch -File $latest -RegEx {
'\d\d:\d\d' { $timestamp = $matches[0] }
'Successfulyl finished running psp_dba_maint_4' { $true; break }
}
if( $found ) {
"The overnight completed at: $timestamp"
}
else {
"Not found"
}
$found = switch ... assigns the output of the switch statement to the variable. The output will be $true if the pattern has been found.
\d\d:\d\d searches for the time (first occurence of a pair of two digits separated by :) within the current line. The automatic variable $matches[0] then gives the matched value, similar to the RegEx -match operator. For a detailed explanation of the RegEx pattern and the ability to experiment with it, see this Regex101.com page.
Although there is no $false output of the switch statement, an empty output evaluates to $false in a boolean context (in this case the if statement), so if the pattern isn't found, the else branch will be entered.
There is a typo in Successfulyl which I just copied from your example ;)
To complement zett42's helpful switch-based answer:
A Select-String solution, as you attempted, is possible, via the -Context parameter, which allows you to capture the lines surrounding a matching line as well:
$time =
Select-String -LiteralPath $latest -Pattern 'psp_dba_maint_4' -Context 3 |
Select-Object -Last 1 |
ForEach-Object {
# Extract and output the 'HH:mm' part of the timestamp from
# the 3rd line above the match (the first element in the pre-context array)
$_.Context.PreContext[0] -replace '^.+ (..:..).+$', '$1'
}
if ($time) {
"The overnight completed at: $time"
} else {
'Do something'
}
Note:
It is much more efficient to pass the file path of the target file to Select-String, via -LiteralPath, than it is to pass the file's content, line by line, via Get-Content.
If you had assigned the whole file-info object (System.IO.FileInfo), as emitted by Get-ChildItem, to $latest, you could alternatively have provided it via the pipeline: $latest | Select-String ...
The type of the match-information objects that Select-String emits is Microsoft.PowerShell.Commands.MatchInfo.
The regex-based -replace operator is used to extract (part of) the time-of-day string from the context line.
For an explanation of the above regex (and substitution, where $1 refers to the first and only capture group, (...)), see this regex101.com page.
As you need to get the Time information only related to the procedure psp_dba_maint_4 you can do:
#Remove empty lines, join strings and split string at "connection 1" = you keep the related information together. Next parse the array of strings for the string which matches "psp_dba_maint_4" and after that extract dateTime
$null = (((Get-Content $latest | ?{$_}) -join $null) -split "connection 1" | ?{$_ -match "psp_dba_maint_4"}) -match '\d{2}:\d{2}:\d{2}'
$Search = $matches[0]
if($search){
"The overnight completed at: $search"
}
else{
"Do something"
}
Alternatively you could parse the log an create objects, e.g.:
$obj = #(
Get-Content $latest | ?{$_} | %{
#If string is connection 1 initialize hashtable/create object
If ($_ -match 'connection 1'){
If ($attrsHT){
new-object -typename psobject -Property $attrsht
}
$attrsHt = #{}
}
Else {
#replace Procedure with Procedure, replace ": " with | and split at |
$split = (($_ -replace "Procedure","Procedure:") -replace ": ","|") -split "\|"
#add key and value to hashtable
$attrsht.add($split[0],$split[1])
}
}
)
gives you the array $obj containing the loginformation:
$obj[0] | fl *
Procedure : 'psp_dba_locklogin'
Message String : -----> login SPRK_SVC_IRISS logged in at 23:55:45 running SRI.exe:AWAITING COMMAND on
rptSPARKLE from host da-pvrep02 is still active
Server message : 2022-10-15 23:56:07:020
Message number : 0, Severity 10, State 1, Line 126
Here's another solution to add to the rest.
if ($found = Get-Content -Path $latest -ReadCount 7 | %{
if ($_ -match 'Setting PrivShield on SPRKSZCBS OUT OF maintenance mode')
{
[regex]::Match($_,'\d\d:\d\d').Value
}
}
) { "The overnight completed at: $found" }
else { 'not found' }
On another note, $latest contains the oldest file and not the newest. Specifying -Descending in your Sort-Object brings the most current files to the top. So, when you select the last one via -Last 1, that is the oldest file and not the newest.

Assign the output message from the console to a variable while running a job

In order to include a timeout, I am running the following job on Powershell:
> $job = Start-Job {$PSOut = net use * "<network path>"/persistent:no}
>> $job | Wait-Job -Timeout 30
>> if ($job.State -eq 'Running') {
>> # Job is still running, cancel it
>> $job.StopJob()
>> } else {
>> # Job completed normally, get the result
>> $myArray = $job | Receive-Job
I would like the variable $PSOut to carry the output in the console window:
"Drive X: is now successfully mapped with "
Or:
"System error 53 encountered..." in case of an error message.
However, $PSOut always returns an empty value.
What I've already tried:
Including the following in the Else branch. That way, I obtain the output if the script executes successfully, but am yet to find a way to do this when the script fails.
$PSOut = (Get-job | Receive-job)
I resolved this problem by including "2>&1" along with the Receive-Job command.
"$job = Start-Job {net use * " & in_NetworkPath & "/persistent:no};"
"$job | Wait-Job -Timeout 60;"
"If ($job.State -eq 'Running')"
"{$job.StopJob();"+ _
"$PSOut = Receive-Job $job 2>&1 | Out-String;"
"throw "Error encountered: Operation timed out" }"
"else"
"{if ($job.Childjobs[0].Error)"
"{$PSOut = Receive-Job $job 2>&1 | Out-String; Throw $PSOut }"
"else"
"{$PSOut = Receive-Job $job | Out-String}}"
This works because Powershell seems to allow the assignment to a variable from Receive-Job only if the text value is in the information stream. The operation 2>&1 moves the text output from the error stream to the information stream. However, you must take note that this would prevent Powershell from throwing the text as an error message.
You can read more about the 2>&1 operation [here][1]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_redirection?view=powershell-7
See also: Powershell: get output from Receive-Job

Using variables outside a function

I am currently writing my first script in Powershell and I am already facing the first problem.
I would like to read the value from a variable in a function so that I can use this variable in another cmd-let later. The problem now is that the variable is only recognized inside the function block and not outside.
How do I get this to work?
Thanks for the help :-)
function Write-Log([string]$logtext, [int]$level=0)
{
if($level -eq 0)
{
$logtext = "[INFO] " + $logtext
$text = "["+$logdate+"] - " + $logtext
Write-Host $text
}
}
Send-MailMessage -To "<xxx#xxx.de>" -Subject "$text" -Body "The GPO backup creation was completed with the following status: `n $text" -SmtpServer "xxx#xxx.de" -From "xxx#xxx.de"
I would like to submit $text
This has to do with variable scoping behavior in PowerShell.
By default, all variables in the caller's scope is visible inside the function. So we can do:
function Print-X
{
Write-Host $X
}
$X = 123
Print-X # prints 123
$X = 456
Print-X # prints 456
So far, so good. But when we start writing to variables outside the function itself, PowerShell transparently creates a new variable inside the function's own scope:
function Print-X2
{
Write-Host $X # will resolve the value of `$X` from outside the function
$X = 999 # This creates a new `$X`, different from the one outside
Write-Host $X # will resolve the value of the new `$X` that new exists inside the function
}
$X = 123
Print-X2 # Prints 123, and 999
Write-Host $X # But the value of `$X` outside is still 123, unchanged
So, what to do? You could use a scope modifier to write to the variable outside the function, but the real solution here is to return the value from the function instead:
function Write-Log([string]$logtext, [int]$level=0, [switch]$PassThru = $true)
{
if($level -eq 0)
{
$logtext = "[INFO] " + $logtext
$text = "["+$logdate+"] - " + $logtext
Write-Host $text
if($PassThru){
return $text
}
}
}
$logLine = Write-Log "Some log message" -PassThru
Send-MailMessage -Subject $logLine ...
if you need to access a variable outside a function in Powershell you might need to use the global variable.
$global:myglobalvariable="This is a PowerShell global variable"
or if its a null
$global:myglobalvariable2 = $null

Powershell warning and error handling

The following code I am writing to replace a batch script which I have below.
$Script:srcpath = ((Get-Location).Path)
$Script:configure = "$Script:srcpath\qtbase\configure.bat"
if (Get-Item "$Script:srcpath\qtbase\configure.bat" -WarningAction (Write-Warning "$Script:configure not found. Did you forget to run 'init-repository'?")) {
continue
}
I am try to rewrite the qt configure batch script:
set "srcpath=%~dp0"
set "configure=%srcpath%qtbase\configure.bat"
if not exist "%configure%" (
echo %configure% not found. Did you forget to run "init-repository"? >&2
exit /b 1
)
if not exist qtbase mkdir qtbase || exit /b 1
echo + cd qtbase
cd qtbase || exit /b 1
echo + %configure% -top-level %*
call %configure% -top-level %*
set err=%errorlevel%
cd ..
exit /b %err%
The error I am getting in PowerShell is the following:
Get-Item : Cannot bind parameter 'WarningAction' to the target. Exception setting
"WarningAction": "Object reference not set to an instance of an object."
At line:4 char:67
+ ... rningAction (Write-Warning "$Script:configure not found. Did you forg ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : WriteError: (:) [Get-Item], ParameterBindingException
+ FullyQualifiedErrorId : ParameterBindingFailed,Microsoft.PowerShell.Commands.GetItemCommand
The problem is the error being thrown is erroneous because the warning am calling is what should take its place to tell people that the item doesn't exist. so run "init-repository".
There isn't a good, "if not exist" in PowerShell.
Okay, there is, but it looks like this:
catch [System.Management.Automation.ItemNotFoundException]
which I am having problems with getting to work.
Why am I doing this before someone asks is because I feel that Microsoft will phase out CMD some time it's good to have updated scripts.
Why it's not working
WarningAction does not work like that.
From the about_CommonParameters documentation:
Determines how the cmdlet responds to a warning from the command. "Continue" is the default value. This parameter works only when the command generates a warning message. For example, this parameter works when a command contains the Write-Warning cmdlet.
So, essentially, the value of WarningAction is Continue by default and can be set to Inquire, SilentlyContinue or Stop. The value it is set to determine what action is taken if the Get-item command throws a warning, not what warning to write if Get-item throws a warning.
You can change the preference variable $WarningPreference to set the WarningAction within the current scope, or precede by a scope modifier.
How to make it work
Test-Path
I second Richard's comment to use Test-Path. This will return True or False, depending on whether it finds the file.
if (-not (Test-Path -Path "$Script:srcpath\qtbase\configure.bat")){
Write-Warning 'Does not exist!'
# do other stuff
continue
}else{
Get-Item $configure
}
try/catch
You could try to catch the exception thrown by Get-Item directly in a try/catch. In a similar way to WarningAction, there is ErrorAction which determines what to do if an error is thrown. Terminating error is required, so ErrorAction is set to Stop.
try{
Get-Item $configure -ErrorAction Stop
}catch [System.Management.Automation.ItemNotFoundException]{
Write-Output "Item not found"
# do other stuff
}catch{
Write-Output "Some other error"
$Error[0] # prints last error
}

passing parameters from a windows batch script into a powershell script

I have a powershell script which uses 'quser' command to extract data regarding users logged onto a series of terminal servers.
I want to add a timestamp to the output files, this timestamp variable is created in a windows batch file which then calls the powershell script passes the computername and timestamp, but the powershell script is erroring with 'Missing ')' in function parameter list'
param(
[CmdletBinding()]
[Parameter(ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[string[]]$ComputerName = 'localhost'
[string[]]$timestamp <========= this is the line I have added
)
If I remove my added line (marked in the code above), the script runs fine
You need to add a comma between the parameters:
param(
[CmdletBinding()]
[Parameter(ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[string[]]$ComputerName = 'localhost',
[string[]]$timestamp
)
Also unless you want multiple timestamps you probably just want it to be a string rather than a string array (so [string]$timestamp).
The error message I get looks like this (except that it is in red). The first error points at the end of the localhost line then there is a knock-on error for what by that time seems to be a spurious ):
PS C:\> param(
>> [CmdletBinding()]
>> [Parameter(ValueFromPipeline=$true,
>> ValueFromPipelineByPropertyName=$true)]
>> [string[]]$ComputerName = 'localhost'
>> [string[]]$timestamp
>> )
>>
At line:5 char:38
+ [string[]]$ComputerName = 'localhost'
+ ~
Missing ')' in function parameter list.
At line:7 char:1
+ )
+ ~
Unexpected token ')' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingEndParenthesisInFunctionParameterList
I'm using Powershell 3 here. Other versions may show the error differently.

Resources