how to catch a System.Diagnostics.Eventing.Reader error in powershell - powershell

The code looks like this:
foreach ($machine in $lbx_workstations.SelectedItems)
{
$temp = (get-winevent -computername $machine -FilterXML $commandString -ErrorAction SilentlyContinue -ErrorVariable eventerr|
Select MachineName, TimeCreated, LevelDisplayName, ID, ProviderName, Message)
blah blah blah...
I made a custom error variable, $eventerr, which works just fine when the get-winevent cmdlet can't find any events that match the criteria in the XML commandstring. However, the problem is this: If the XML commandstring is invalid, the error is created in the $error variable instead of the $eventerr variable. I'd like to get that error stored in my custom error variable, but I don't know where it is coming from or what is generating it. Or why it isn't already in my custom variable, actually. When I look at these two different types of errors, this is the output I get:
PS C:\Temp> $error[0].fullyqualifiederrorid
System.Diagnostics.Eventing.Reader.EventLogException,Microsoft.PowerShell.Commands.GetWinEventCommand
PS C:\Temp> $error[1].fullyqualifiederrorid
NoMatchingEventsFound,Microsoft.PowerShell.Commands.GetWinEventCommand
I can catch the "NoMatchingEventsFound" error in the custom variable, but not the System.Diagnostics.Eventing... error.
Is there any way to get the "System.Diagnostics.Eventing... error into my custom error variable?

It's probably happening when PowerShell is trying to convert $commandString into an XmlDocument (the type of the -FilterXml parameter). I don't think you'll be able to capture the error in your error variable since it is happening before the call into Get-WinEvent. Your best bet is to validate $commandString before trying to pass it to Get-WinEvent.
try
{
$xmlDoc = New-Object 'Xml.XmlDocument'
$xmlDoc.LoadXml( $commandString )
}
catch
{
# Or do some other kind of error handling. You're the driver!
Write-Error ('Oh no! Bad XML! What are you thinking?!')
}
$lbx_workstationg.SelectedItems |
ForEach-Object { Get-WinEvent -ComputerName $_ -FilterXml $commandString -ErrorAction SilentlyContinue -ErrorAction eventerr } |
Select-Object MachineName, TimeCreated, LevelDisplayName, ID, ProviderName, Message
That being said, I have noticed that sometimes using the -ErrorVariable doesn't result in all the errors from getting put in the error variable specified. I never found out why. I usually end up falling back to using $Error. You can get the errors specific to your command by proper indexing:
$errorCount = $Error.Count
$lbx_workstationg.SelectedItems |
ForEach-Object { Get-WinEvent -ComputerName $_ -FilterXml $commandString -ErrorAction SilentlyContinue } |
Select-Object MachineName, TimeCreated, LevelDisplayName, ID, ProviderName, Message
$winEventError = $Error[0..($Error.Count - $errorCount)]

The two different errors make different types of error messages. These types have different properties.
$eventerr.message holds the text I need if the error is
TypeName: System.Management.Automation.CmdletInvocationException
The specified query is invalid
$eventerr.exception holds the error text if the error is this type:
TypeName: System.Management.Automation.ErrorRecord
No events were found that match the specified selection criteria.
I'm not clear on how a single variable (eventerr) can hold different object types. But this seems to be the case. If anyone has a good explanation for how this can be, I'd sure like to hear it. Meanwhile, I'm going to mark this as the answer.

Related

Powershell - MSExchange Set-Contact How to throw an error when cmdlet returns error message

I have the following code that updates contact records in MS exchange. My goal is that if it fails to update the record, it goes into my catch block to log that there was an error. Instead, it's just returning the following in the log and moving on, not going to the catch block. I'm newer to powershell but i'm assuming the error msg is not a ps exception, and is reporting the error on the external cmdlet. Is there any way to check that the cmdlet encountered and error so I can log it/go down an error path?
snippet of code I'm running:
try{
$msxchangeInfo = Get-Contact | Where-Object {$_.WindowsEmailAddress -eq "$user_search_email"}
if ($msxchangeInfo)
{
write-host "$(Get-TimeStamp) $user_search_email found...."
$msxchangeInfo = Get-Contact -Filter "WindowsEmailAddress -eq '$($user_search_email)'" |
Select-Object -Property WindowsEmailAddress, Manager, FirstName, LastName, MobilePhone,
DisplayName, Company, Department, Identity, StreetAddress, StateOrProvince, PostalCode,
CountryOrRegion, City, Phone
$msxchangeInfoCountryOrRegion = $msxchangeInfo | Select -ExpandProperty CountryOrRegion
if($msxchangeInfoCountryOrRegion -ne $officeCountry -and $officeCountry -ne "")
{
/*CODE THAT IS RETURNING ERROR MESSAGE BUT NOT THROWING EXCEPTION!*/
Set-Contact -Identity $msxchangeInfoIdentity -CountryOrRegion $officeCountry}
}
}
catch
{
write-host("$(Get-TimeStamp) *****ERROR*****: ERROR ATTEMPTING TO CREATE NEW CONTACT RECORD:
$($displayName) . CHECK ERROR BELOW")
write-host($_.Exception|format-list -force)
write-host("$(Get-TimeStamp) Adding user to error ticket list")
$errMsg = $error[0].ToString() + $error[0].InvocationInfo.PositionMessage
$errMsg -replace ',','|'
}
However the code is never hitting the catch block. It is not throwing an error, it's just logging the cmdlet error message and continuing. How do I break it on the cmdlet error of set-contact?
This is the output in the log (as you can see, not logging my custom error message noted above):
Account Environment TenantId TenantDomain AccountType
------- ----------- -------- ------------ -----------
gggggggggggggg-gggggg-ggggggg-gg AzureCloud gggggggggggg-ggggg-gggg-gg TestTenant.com ServicePrincipal
Cannot process argument transformation on parameter 'CountryOrRegion'. Cannot convert value "Not Found" to type
"Microsoft.Exchange.Data.Directory.CountryInfo". Error: "The ISO-3166 2-letter country/region code or friendly name
"Not Found" does not represent a country/region."
+ CategoryInfo : InvalidData: (:) [Set-Contact], ParameterBindin...mationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Set-Contact
+ PSComputerName : outlook.office365.com
You can force a specific call to a cmdlet to halt on error via the -ErrorAction common parameter:
Set-Contact -Identity $msxchangeInfoIdentity -CountryOrRegion $officeCountry -ErrorAction Stop
You can also set this as the default behavior for any call in the current scope by modifying the $ErrorActionPreference variable at the start of your script:
$ErrorActionPreference = 'Stop'
... or, alternatively use the $PSDefaultParameterValues variable to make specific cmdlets in the current scope always take on this behavior on error:
$PSDefaultParameterValues['Set-Contact:ErrorAction'] = 'Stop'

Retrieving Computer Names That Produced an Error From Invoke-Command

I'm working on speeding the execution of a script and long story short, the core of it would look similar to this (minus Measure-Command):
$devices = Get-Content "list.txt"
Measure-Command{
try{
$Result = Invoke-Command -ComputerName $devices -ScriptBlock {
Get-LocalUser | Select-Object -Property #{N="Computer"; E={$env:COMPUTERNAME}},
Name, Enabled, PasswordChangeableDate, PasswordExpires, UserMayChangePassword,
PasswordRequired, PasswordLastSet, LastLogon,
#{n="Groups"; E={
$user = $_
Get-LocalGroup | Where-Object { $user.SID -in ($_ | Get-LocalGroupMember | Select-Object -ExpandProperty "SID") } | Select-Object -ExpandProperty "Name"
}}
} 2>> "errors.txt"
}catch{
Write-Host "Uh oh..." -ForegroundColor Red
Write-Host $Error[0]
}
}
What I'm trying to figure out is, in the case of an error on one of the devices, I want to store that device name in a separate file. In the past I was doing all of this process via a foreach loop with try/catch, which made this part very easy. I'm looking to avoid that with this solution.
Right now, I'm using 2>> "errors.txt" courtesy of this post, but this records the full error, which I don't want. Example:
[EXAMPLE DEVICE] Connecting to remote server EXAMPLE DEVICE failed with the following error message : WinRM cannot process the
request. The following error occurred while using Kerberos authentication: Cannot find the computer EXAMPLE DEVICE. Verify that
the computer exists on the network and that the name provided is spelled correctly. For more information, see the
about_Remote_Troubleshooting Help topic.
+ CategoryInfo : OpenError: (EXAMPLE DEVICE:String) [], PSRemotingTransportException
+ FullyQualifiedErrorId : NetworkPathNotFound,PSSessionStateBroken
I'd like the only record the name of the device (in this case "EXAMPLE DEVICE") in the file. Is there a way to do this?
One way to handle is to set -ErrorActions SilentlyContinue -ErrorVariable Errs.
This enables the program to flow normally without bleeding red errors all over the screen. Then you can look at the error records stored in $Errs to report on which machines had issues. In your case $Errs.TagrgetObject.
Demo might be something like
Invoke-Command -ComputerName $Computers -ScriptBlock { "Whatever..." } -ErrorAction SilentlyContinue -ErrorVariable Errs
# PostOp Check which machines had errors:
$Errs.TargetObject
This should return:
MrBogus
MrsBogus
Of course, if you want to format that more robustly you can do any arbitrary processing on the error records.

Apply conditional to output of a commandlet

I want to output the result of the commandlet (Invoke-Command) on success and add a custom message if the result is null. The code as shown below produces the desired results except in the event of a null response, it simply outputs nothing on that line.
I can not pipe directly to an if statement, nor can I output on 2 opposing conditions (True & False). Is it possible to get a custom response on $null while not suppressing the normal output on success?
Invoke-Command -ComputerName PC1, PC2, PC3 -Scriptblock {get-eventlog system | where-object {$_.eventid -eq 129} | select MachineName, EventID, TimeGenerated, Message -last 1}
If you run the example code block assuming that PC1 and PC3 have the event ID but PC2 does not, the output will simply skip PC2.
I want to output something like "Event Not found" in that case.
Placing the entire thing in a loop and then running the results through another conditional loops destroys performance so that is not an ideal solution.
I would create a new object for returning from Invoke-Command. So you are sure you will receive from every host something even the event log is not present. And might you can change get-eventlog to Get-WinEvent. Get-WinEvent was for my tasks the most time faster than get-eventlog.
[System.Management.Automation.ScriptBlock]$Scriptblock = {
[System.Collections.Hashtable]$Hashtable = #{
WinEvent = Get-WinEvent -FilterHashtable #{ LogName = 'System'; Id = 129 } -MaxEvents 1 -ErrorAction SilentlyContinue #-ErrorAction SilentlyContinue --> otherwise there is an error if no event is available
}
return (New-Object -TypeName PSCustomObject -Property $Hashtable)
}
Invoke-Command -ComputerName 'PC1', 'PC2', 'PC3' -Scriptblock $Scriptblock

Checking if a local user account/ group exists or not with Powershell

There are two parts to this question.
I want to check if a local user exists or not before I go ahead and create it.
So far I've come up with a simple script to check if a local user exists or not. Here's the script to check if a user exists before I go ahead and create it.
$password = ConvertTo-SecureString -String "password" -AsPlainText -Force
$op = Get-LocalUser | Where-Object {$_.Name -eq "testuser1"}
if ( -not $op)
{
New-LocalUser testuser1 -Password $password | Out-Null
}
I tested this one out on my setup and it works fine for the most part without throwing any exception. Is there a better, quicker way to check if a user exists ? Also, is the script I'm using foolproof i.e. would it be better to handle it using ErrorAction or using try....catch ?
I'll be using this script for checking more than a couple of user accounts before I go ahead and create them.
Why is $op different in the following cases ?
CASE 1
CASE 2
I understand that Out-String is the reason behind this difference in output but I would've expected the output to have been more than just testuser1 in CASE 1.
I'm new to Powershell so can someone please help me understand why there's a difference in output ?
Use Try/Catch, most of the time it's faster to just ask and let Powershell handle the searching ;)
Especially with Long User lists, retrieving all the users and then iterating trough all of them will slow things down, just asking for a specific user is much faster but you need to handle the error if the user does not exist.
See example below:
Clear-Host
$ErrorActionPreference = 'Stop'
$VerbosePreference = 'Continue'
#User to search for
$USERNAME = "TestUser"
#Declare LocalUser Object
$ObjLocalUser = $null
try {
Write-Verbose "Searching for $($USERNAME) in LocalUser DataBase"
$ObjLocalUser = Get-LocalUser $USERNAME
Write-Verbose "User $($USERNAME) was found"
}
catch [Microsoft.PowerShell.Commands.UserNotFoundException] {
"User $($USERNAME) was not found" | Write-Warning
}
catch {
"An unspecifed error occured" | Write-Error
Exit # Stop Powershell!
}
#Create the user if it was not found (Example)
if (!$ObjLocalUser) {
Write-Verbose "Creating User $($USERNAME)" #(Example)
# ..... (Your Code Here)
}
About outputting certain data, I recommend that you explicitly define what you want to output, this way their will be no surprises and it makes thing clearer in your code.
See the example below, I explicitly defined the 3 properties I wanted and then forced it into a Table-View, to finish I converted it to a string, so no surprises for me any more ;)
Get-LocalUser | Select Name, Enabled, PasswordLastSet | Format-Table | Out-String
Example output
Name Enabled PasswordLastSet
---- ------- ---------------
Administrator False
DefaultAccount False
Gast False
Test-Gebruiker True 24-12-2017 01:58:12
You can use measure to check if user exist
$op = Get-LocalUser | where-Object Name -eq "yourusername" | Measure
if ($op.Count -eq 0) {
# DO SOMETHING
} else {
# REPORT SOMETHING
}
Write-Host calls ToString() method implementation of an object behind the scenes.
Write-Host $op
is equivalent to
Write-Host $op.ToString()
Get-LocalUser | Where-Object {$_.Name -eq "testuser1"} returns a LocalUser object. LocalUser.ToString() implementation returns Name property, that's why you see testuser1 as output.
Out-String on the other hand, converts the whole output of the command, which is a table representation of LocalUser object with 3 properties.
In Case 2, you can only use that output as a String object, whereas in Case 1, $op is a LocalUser object that you can manipulate or access properties. For example, you can print more properties:
Write-Host $op.Name, $op.Enabled, $op.Description
In order to see all the properties/methods available on an object, run Get-Member cmdlet:
$op | Get-Member
$UserID = Get-LocalUser -Name 'UserName' -ErrorAction SilentlyContinue
if($UserID){
Write-Host 'User Found'
}else{
Write-Host 'User Not Found'
} #end if user exists

Extracting part of a host name with select-object

I have a simple powershell function where I provide the log type and event and it scans all of our SQL servers. it works except the host name is returned as hostname.domain.local. I want it to return just the host name. I've tried machinename.split('.') and substring and it won't work. I've tried putting the select-object into a separate variable and was going to join it with the rest of the columns, but it takes too long to run.
Here is my sample scrap code i'm testing with before I change my function along with the commented out parts that didn't work. Looked around and found lots of resources about the commands, but they don't work when I try to use them in my script.
The error I keep getting is A positional parameter cannot be found that accepts argument '. '.
$servers = Get-Content -literalpath "C:\temp\sql_servers3.txt"
#$server
#$result =
ForEach($box in $servers) {Get-Eventlog -ComputerName $box -LogName
application -After 1-4-2018 -Entrytype Error | Where {$_.source -notin
'Perfnet','Perflib', 'ntfs', 'vss'}| select-object -property MachineName}
#$result_Host_name = select-object -inputobject $result -property
'MachineName'
#'TimeGenerated', 'MachineName'.Split('.')[1], 'EventID','message'}
#| Where {$_.source -notin 'Perfnet','Perflib', 'ntfs', 'vss'} 0
#return $result_Host_name
What you are looking for is a "Calculated Property" when using Select-Object.
| Select-Object #{n='HostName';e={($_.MachineName -split '\.')[0]}}