Printing the error in try catch block in powershell - exception

Here is my script which returns a boolean
param($fileName, $path, $contextMenuItem, $automationDLLPath)
function CloseWindowsExplorer()
{
(New-Object -comObject Shell.Application).Windows() | foreach-object {$_.quit()}
}
Try
{
Import-Module $automationDLLPath
# Open the explorer window in a maximized form
Start-Process explorer $path -WindowStyle Maximized
Start-Sleep 1
Get-UIAActiveWindow
# Get the "Items View" in Explorer to go through all the lements
$list = Get-UIAList -Name 'Items View' -TimeOut 30000;
# Get the file specified in the feature file from the Items View
# Added a sleep because the VM takes time to perform the functions
Start-Sleep 1
$file = $list | Get-UIAListItem -Name $fileName;
# Perform a single click on the file to invoke a right click on it
Invoke-UIAListItemSelectItem -InputObject $file -ItemName $fileName;
# Added a sleep because the VM takes time to perform the functions
Start-Sleep 1
# Invoke the right click on the selected file
$menu = Invoke-UIAControlContextMenu -InputObject $file;
Start-Sleep 1
# select our context menu item
$menuItem = Get-UIAMenuItem -InputObject $menu $contextMenuItem -TimeOut 30000;
# Display error if the required item in the context menu is not found
if( $null -eq $menuItem){
%{ Write-Host 'cannot find menuItem' }
}
# Invoke the item if found in the context menu
else{
Invoke-UIAMenuItemClick -InputObject $menuItem
}
# close the windows explorer and return true
CloseWindowsExplorer
Write-Output "true"
}
Catch
{
# close the explorer window as a part of teardown and return false to reflect test failure
Write-Output "false"
CloseWindowsExplorer
}
I want the script to print the exact exception that was caught as well as return a boolean but in this case it is just returning false when the script fails. Any help is appreciated
Basically I need to print the exception as if the try catch block does not exist.

You need to use the special variable $_
This small example shows how it works:
try {
testmenow
} catch {
Write-Host $_
}
$_ is an object so you can do
$_|gm
in the catch block in order to see the methods you can call.

Related

Powershell not returning correct value

As some background, this should take an excel file, and convert it to PDF (and place the PDF into a temporary folder).
E.g. 'C:\Users\gjacobs\Desktop\test\stock.xlsx'
becomes
'C:\Users\gjacobs\Desktop\test\pdf_merge_tmp\stock.pdf'
However, the new file path does not return correctly.
If I echo the string $export_name from within the function, I can see that it has the correct value: "C:\Users\gjacobs\Desktop\test\pdf_merge_tmp\stock.pdf".
But once $export_name is returned, it has a different (incorrect value): "C:\Users\gjacobs\Desktop\test\pdf_merge_tmp C:\Users\gjacobs\Desktop\test\pdf_merge_tmp\stock.pdf".
function excel_topdf{
param(
$file
)
#Get the parent path
$parent = Split-Path -Path $file
#Get the filename (no ext)
$leaf = (Get-Item $file).Basename
#Add them together.
$export_name = $parent + "\pdf_merge_tmp\" + $leaf + ".pdf"
echo ($export_name) #prints without issue.
#Create tmp dir
New-Item -Path $parent -Name "pdf_merge_tmp" -ItemType "Directory" -Force
$objExcel = New-Object -ComObject excel.application
$objExcel.visible = $false
$workbook = $objExcel.workbooks.open($file, 3)
$workbook.Saved = $true
$xlFixedFormat = “Microsoft.Office.Interop.Excel.xlFixedFormatType” -as [type]
$workbook.ExportAsFixedFormat($xlFixedFormat::xlTypePDF, $export_name)
$objExcel.Workbooks.close()
$objExcel.Quit()
return $export_name
}
$a = excel_topdf -file 'C:\Users\gjacobs\Desktop\test\stock.xlsx'
echo ($a)
The issue you're experiencing is caused by the way how PowerShell returns from functions. It's not something limited to New-Item cmdlet. Every cmdlet which returns anything would cause function output being altered with the value from that cmdlet.
As an example, let's take function with one cmdlet, which returns an object:
function a {
Get-Item -Path .
}
$outputA = a
$outputA
#### RESULT ####
Directory:
Mode LastWriteTime Length Name
---- ------------- ------ ----
d--hs- 12/01/2021 10:47 C:\
If you want to avoid that, these are most popular options (as pointed out by Lasse V. Karlsen in comments):
# Assignment to $null (or any other variable)
$null = Get-Item -Path .
# Piping to Out-Null
Get-Item -Path . | Out-Null
NOTE: The behavior described above doesn't apply to Write-Host:
function b {
Write-Host "bbbbbb"
}
$outputB = b
$outputB
# Nothing displayed
Interesting thread to check if you want to learn more.

Use a parameter switch to change how a function behaves

My main PowerShell code runs a function that logs to the Windows eventlog. If the level is error it uses a separate event ID which then our monitoring will pick up that exact ID and run an action. However, if I want to specify in the parameter of the main script (not the function) that this time running it use a different Event ID so it will NOT action monitoring, I don't know where to even start on that.
Is there a way to provide a switch parameter in the main script like $NoAlert which then changes the Event ID in the function?
The function of logging lives in a PowerShell module I created. I am importing the module at the beginning of the script and then calling the function during the main script body.
Here is the function:
function WriteLog-SRTProd {
Param(
[string]$logT,
[Parameter(Mandatory=$true)][string]$level,
[String]$LogFileDirT = "\\ServerA\Logs"
)
$RSLogfileT = (Get-ChildItem -Path $LogFileDirT |
sort LastWriteTime |
select -Last 1).Name
## make sure a level is correctly selected (mandatory)
if ("Error","Info","Warn" -NotContains $Level) {
throw "$($Environment) is not a valid name! Please use 'Error', 'Warn', or 'Info'"
}
if ($Level -eq "Info") {
Add-Content -Path "$LogFileDirT\$RSLogFileT" -Value "$(Get-Date -format MM-dd-yyyy::HH:mm:ss) INFO $logT"
Write-EventLog -LogName Application -Source TEST_MAINT -EntryType Information -EventId 100 -Message $logT -Category 0
}
if ($Level -eq "Warn") {
Add-Content -Path "$LogFileDirT\$RSLogFileT" -Value "$(Get-Date -format MM-dd-yyyy::HH:mm:ss) WARN $logT"
Write-EventLog -LogName Application -Source TEST_MAINT -EntryType Warning -EventId 200 -Message $logT -Category 0
}
if ($Level -eq "Error") {
Add-Content -Path "$LogFileDirT\$RSLogFileT" -Value "$(Get-Date -format MM-dd-yyyy::HH:mm:ss) ERROR $logT"
Write-EventLog -LogName Application -Source TEST_MAINT -EntryType Error -EventId 300 -Message $logT -Category 0
}
}
I'd like to run my script like this. When the $NoAlert is passed, it will send that switch to the function. Is this possible? Can I just add the switch in both places and use an if statement in the function for when the NoAlert switch is used?
PS C:\> .\Maintenance.ps1 -NoAlert
Param(
[switch]$NoAlert
)
WriteLog-SRTProd -level Error -logT "Custom Error Message"
I have created own function for logging and stored/installed as module, below is the part of my log module :
you can customize the write statements and add your code for event log. I have added 'NoAction' enum member as per your requirements.
I have used one Enum to separate the log levels
Enum Severity
{
Error = 3
Warning = 4
Informational = 6
Debug = 7
Verbose = 8
NoAction = 0 # AS PER YOUR REQUIREMENTS
}
function Write-Log()
{
[cmdletbinding()]
param
(
[Parameter(Position=0,mandatory=$true)]
[Severity] $LogLevel,
[Parameter(Position=1,mandatory=$true)]
[String] $Message
)
$TimeStamp = "$(Get-Date -format HH:mm:ss)" ;
Switch($LogLevel)
{
([Severity]::Error.ToString())
{
Write-Error "`t$TimeStamp : $Message`n" -ErrorAction Stop
break;
}
([Severity]::Warning.ToString())
{
Write-Warning "`t$TimeStamp : $Message`n" -WarningAction Continue
break;
}
([Severity]::Informational.ToString())
{
Write-Information "INROMATION:`t$TimeStamp : $Message`n" -InformationAction Continue
break;
}
([Severity]::Verbose.ToString())
{
Write-Verbose "`t$TimeStamp : $Message`n"
break;
}
([Severity]::NoAction.ToString())
{
Write-Verbose "`t$TimeStamp : $Message`n"
break;
}
} # END OF SWITCH
} # END OF FUNCTION
Sample Call :
Write-Log -LogLevel ([Severity]::Informational) -Message "test log message using info level"
Output :
INROMATION: 09:40:15 : test log message using info level
I have decided to just add a new parameter to both function and main script named $NoAlert. I have added an If($NoAlert){WriteLog-SRPProd -NoAlert} to the main script (messy, but its what I needed done). then in the Function, If($NoAlert){EventID 111}. so basically I am using the switch in the main script that then calls the NoAlert switch in the function. This is all done with a few added If/Else statements.
Hopefully that makes sense. Like I said its not the best answer, but I wanted to get it done and still provide an answer here in this post.

Logging to console and file with function passing back return code

I've decided it makes sense that functions being called by a Powershell script should
Log the same output to both a log file and to the console and
should return a status indicating success/failure.
I found a way to do this but it seems ridiculously cumbersome and backwards (illustrated below). I'm thinking this is such a basic and essential capability for any scripting language and that I must be really lost and confused to be doing something in such a backwards way. I'm pretty new to PowerShell but come from a C# background.
I ended up adding -PassThru to every Add-Content statement in the function so the log entry will be coming back in the pipeline as item of an Object[] collection. I then am passing back a final boolean item in the Object[] collection which is the status of the function.
# Main script c:\temp\test1.ps1
Function Write-FunctionOutputToConsole {
Param ([Object[]] $FunctionResults)
foreach ($item in $FunctionResults) {
if ($item -is [System.Array]) {
Write-Host $($item)
}
}
}
Function Get-FunctionReturnCode {
Param ([Object[]] $FunctionResults)
if ($FunctionResults[-1] -is [System.Boolean]) {
Return $FunctionResults[-1]
}
}
. c:\temp\test2.ps1 #pull in external function
$LogFile = "c:\temp\test.log"
$results = FunctionThatDoesStuff -LogFile $LogFile -DesiredReturnValue $true
Write-FunctionOutputToConsole -FunctionResults $results
$FunctionReturnCode = Get-FunctionReturnCode -FunctionResults $results
Add-Content -Path $LogFile -Value "$(Get-Date -Format G) Logging in Main: returnValue=$FunctionReturnCode" -PassThru
# Do some logic based on $FunctionReturnCode
External function
# c:\temp\test2.ps1
function FunctionThatDoesStuff {
Param(
[string] $LogFile,
[bool] $DesiredReturnValue
)
Add-Content -Path $LogFile -Value "-----------------------------------------" -PassThru
Add-Content -Path $LogFile -Value "$(Get-Date -Format G) returnValue=$DesiredReturnValue" -PassThru
Add-Content -Path $LogFile -Value "$(Get-Date -Format G) line 1 being logged" -PassThru
Add-Content -Path $LogFile -Value "$(Get-Date -Format G) line 2 being logged" -PassThru
return $DesiredReturnValue
}
Console Output:
PS C:\Temp> c:\temp\test1.ps1
-----------------------------------------
7/19/2018 3:26:28 PM returnValue=True
7/19/2018 3:26:28 PM line 1 being logged
7/19/2018 3:26:28 PM line 2 being logged
7/19/2018 3:26:28 PM Logging in Main: returnValue=True
Log File
PS C:\Temp> get-content c:\temp\test.log
-----------------------------------------
7/19/2018 3:29:59 PM returnValue=True
7/19/2018 3:29:59 PM line 1 being logged
7/19/2018 3:29:59 PM line 2 being logged
7/19/2018 3:29:59 PM Logging in Main: returnValue=True
As you can see this results in the identical information in the Console and logging file.
I think you're misunderstanding how PowerShell works. For one thing, the information whether or not the last command was successful is automatically stored in the automatic variable $?. In case of an error cmdlets will throw an exception that can be caught for error handling (see also). There is no need to signal success or error status with a return value. Also, PowerShell by default returns all uncaptured output from a function. The return keyword is just for control flow.
I would implement a logging function somewhat like this:
function Write-LogOutput {
[CmdletBinding()]
Param(
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
[string[]]$Message,
[Parameter(Position=1, Mandatory=$false)]
[ValidateScript({Test-Path -LiteralPath $_ -IsValid})]
[string]$LogFile = '.\default.log',
[Parameter(Mandatory=$false)]
[switch]$Quiet,
[Parameter(Mandatory=$false)]
[switch]$PassThru
)
Process {
$Message | ForEach-Object {
$msg = "[{0:yyyy-MM-dd HH:mm:ss}]`t{1}" -f (Get-Date), $_
if (-not $Quiet.IsPresent) {
$msg | Out-Host
}
$msg
} | Add-Content $LogFile
if ($PassThru.IsPresent) {
$Message
}
}
}
and then use it like this:
function FunctionThatDoesStuff {
# ...
# something that should be logged, but not returned
'foo' | Write-LogOutput -LogFile 'C:\path\to\your.log'
# something that should be logged and returned by the function
'bar' | Write-LogOutput -LogFile 'C:\path\to\your.log' -PassThru
# something that should be returned, but not logged
'baz'
# ...
}
$result = FunctionThatDoesStuff
# Output:
# -------
# [2018-07-19 23:44:07] foo
# [2018-07-19 23:44:07] bar
$result
# Output:
# -------
# bar
# baz

Exit a PowerShell function but continue the script

This might seem like a very very stupid question, but I can't really figure it out. I'm trying to have the function stop when it finds its first hit (match) and then continue with the rest of the script.
Code:
Function Get-Foo {
[CmdLetBinding()]
Param ()
1..6 | ForEach-Object {
Write-Verbose $_
if ($_ -eq 3) {
Write-Output 'We found it'
# break : Stops the execution of the function but doesn't execute the rest of the script
# exit : Same as break
# continue : Same as break
# return : Executes the complete loop and the rest of the script
}
elseif ($_ -eq 5) {
Write-Output 'We found it'
}
}
}
Get-Foo -Verbose
Write-Output 'The script continues here'
Desired result:
VERBOSE: 1
VERBOSE: 2
VERBOSE: 3
We found it
The script continues here
I've tried using break, exit, continue and return but none of these get me the desired result. Thank you for your help.
As was mentioned, Foreach-object is a function of its own. Use regular foreach
Function Get-Foo {
[CmdLetBinding()]
Param ()
$a = 1..6
foreach($b in $a)
{
Write-Verbose $b
if ($b -eq 3) {
Write-Output 'We found it'
break
}
elseif ($b -eq 5) {
Write-Output 'We found it'
}
}
}
Get-Foo -Verbose
Write-Output 'The script continues here'
The scriptblock you are passing to ForEach-Object is a function in its own right. A return in that script block just returns from the current iteration of the scriptblock.
You'll need a flag to tell future iterations to return immediately. Something like:
$done = $false;
1..6 | ForEach-Object {
if ($done) { return; }
if (condition) {
# We're done!
$done = $true;
}
}
Rather than this, you may be better using a Where-Object to filter the pipeline objects to only those that you need to process.

Parsing text type log file

I have just started PowerShell today
I have this type of log files with any number of tests:
Plan test.pln - 1 error
[#VERSION-TestPlanGenerator#]3.8.0.0018
HW# VS4_1
[#TC#] test 1
\\APPS-EUAUTO1\C$\...\Temp\FXSAPIDebugLogFile.txt - The process cannot access the file because it is being used by another process.
[APPS-EUAUTO1] [prep] Setting agent options and random seed...
[APPS-EUAUTO1] [info] Initial Seed : 124426621
[APPS-EUAUTO1] [info] Current seed : 96010
[APPS-EUAUTO1] [info] rt1 t1
[APPS-EUAUTO1] [debug] rt1 t1
[#WARNING#][APPS-EUAUTO1] [warning] rt1 t1 ( Screen shot : D:\...\[APPS-EUAUTO1] 03-28-14 11-29-22.png)
[#WARNING#][APPS-EUAUTO1] [warning] Unhandled error detected ! ( Screen shot : D:\...\[APPS-EUAUTO1] 03-28-14 11-29-22.png)
[#ERROR#][APPS-EUAUTO1] [error] rt1 t1 ( Screen shot : D:...\[APPS-EUAUTO1] 03-28-14 11-29-22.png)
Occurred in fnMsg at ..\functions\f_common.inc(456)
Called from t1 at test.t(10)
Called from rt1 at test.t(5)
[#TC#] test 2
[APPS-EUAUTO1] [prep] Setting agent options and random seed...
[APPS-EUAUTO1] [info] Current seed : 177041
[APPS-EUAUTO1] [info] rt2 t2
[APPS-EUAUTO1] [debug] rt2 t2
I need to get all the tests in an array in witch each element will have:
a string Name ( ex: test 1)
a boolean Error (ex: true for test 1 because there is a [#WARNING#]
or a [#ERROR#] message present)
a array Messages with all the messages (ex: for test 2 all 4
messages)
and at the end I will like to export this array to a html file.
All tests begins with [#TC#].
I'm having problems with the reading part.
I have tried a couple of things from different sites but, it doesn't not seem to work for me :
Function Import-MyLog1 {
# -----------------------------------------------------------------------
Function Get-Log {
# Reads the log file into memory.
Try {
Get-Content -path "res.txt" -ErrorAction Stop -Delimiter "[#TC#]"
}
Catch {
Write-Error "The data file is not present"
BREAK
}
} # End: Function Get-Log
# -----------------------------------------------------------------------
Function Get-Record {
Param ($Log)
for ($i=1; $i -lt $Log.Length; $i++) { # ignore the header
$Testcase = $Log[$i]
New-Object PSobject -Property #{
Name = $Testcase[0]
Data = $Testcase[3..6]
}
}
} # End: Function Get-Record
# Load the log into memory
$Log = Get-Log
$Records = Get-Record -Log $Log
$Records # Added only to see the current progress.
} #End: Function Import-MyLog1
clear
Import-MyLog1
this is the final code for who might need a example :
Function Get-TxtLog {
Param ($File)
# Reads the log file into memory.
Try {
Get-Content -path $File -ErrorAction Stop -Delimiter "[#TC#]"
} Catch {
Write-Error "The data file is not present"
BREAK
}
} # End: Function Get-TxtLog
# -----------------------------------------------------------------------
Function Get-Testcase {
Param ($TxtLog)
for ($i=1; $i -lt $TxtLog.Count; $i++) { # $i=1 to ignore the header
$Testcase = $TxtLog[$i].split("`n")
$Output = New-Object PSobject -Property #{
Name = $Testcase[0].Trim()
Messages = $Testcase[1..($Testcase.count)] | ?{!($_ -match "\[#TC#]")} | ForEach-Object -process {$_.Trim()}
}
$Error = $( if($Output.Messages -match ("\[#ERROR#]|\[#WARNING#]")) {$true} else {$false} )
$Output|Add-Member -MemberType NoteProperty -Name "Error" -value $Error
$Output|Add-Member -MemberType NoteProperty -Name "Runtime" -value $null # will be added later
$Output|Add-Member -MemberType NoteProperty -Name "Data" -value $null # will be added later
$Output # to pipe the object
}
} # End: Function Get-Testcase
# -----------------------------------------------------------------------
# Load the log into memory
$TxtLog = Get-TxtLog -file "D:\XHostMachine\Results\res.txt"
$Records = Get-Testcase -TxtLog $TxtLog
$Records | Format-Table
If you just started PowerShell today then I can only imagine what you'll be doing with it in a year's time... You have started very well in my opinion.
Now, you seem to want to make everything into a function, which I suppose there is little harm in, but personally it seems overkill. When I pasted your test code into my ISE the first thing I did was comment out the first line and 28th line through the end of it. There just doesn't seem to be a need for all that.
Next I added a parameter to the Get-Log function so that a path can be provided if desired, and if excluded it will default to your res.txt file.
Function Get-Record {
Param ($Log)
for ($i=1; $i -lt $Log.Count; $i++) { # ignore the header
$Testcase = $Log[$i].split("`n")
$Output = New-Object PSobject -Property #{
Name = $Testcase[0]
Data = $Testcase[3..($Testcase.count)]|?{!($_ -match "\[#TC#]")}
}
$Output|Add-Member -MemberType NoteProperty -Name "Error" -value $(if($Output.data -match "^.+?(\[#ERROR#]|\[#WARNING#])"){$true}else{$false})
$Output
}
} # End: Function Get-Record
After that I looked at the value of $Log once it was gotten. You end up with an array with 3 strings in it. That's all fine and good, but what you really want is an array with 3 arrays in it if you ask me. Right now $Log[0] is a string with 4 lines of text, and you'd be better off with an array of 4 strings... so let's go that route first. I modified your Get-Record to accomplish that.
Function Get-Record {
Param ($Log)
for ($i=1; $i -lt $Log.Count; $i++) { # ignore the header
$Testcase = $Log[$i].split("`n")
You'll notice the split is done on n which is the powershell NewLine character. Then I updated the object you created to exclude the [#TC#] which was used as a delimiter, and assigned it a variable instead of just outputting it. Once I had that $Output variable I tested it for[#ERROR#]and[#WARNING#]` using a regex match and added a new Error property to the object depending on if an error was found or not.
$Output = New-Object PSobject -Property #{
Name = $Testcase[0]
Data = $Testcase[3..($Testcase.count)]|?{!($_ -match "\[#TC#]")}
}
$Output|Add-Member -MemberType NoteProperty -Name "Error" -value $(if($Output.data -match "^.+?(\[#ERROR#]|\[#WARNING#])"){$true}else{$false})
$Output
}
} # End: Function Get-Record
Then I pretty much passed the rest off as is except added my path to the log that I made from your example text.
# Load the log into memory
$Log = Get-Log c:\temp\test.log
$Records = Get-Record -Log $Log
$Records # Added only to see the current progress.
#} #End: Function Import-MyLog1
#
#clear
#Import-MyLog1
Now, you could clean it up a bit I suppose by trimming blank space from the beginning of lines if you wanted, but that's just a matter of taste. But it gives you 2 entries in $Records each with the name you wanted, and the data lines, and a boolean Error property.