Suppress Error output Where-Object on a cmd command - powershell

I'm trying to suppress an error output from a Where-Object of a cmd command (gpresult /r), first I saved the output of the gpresult to a variable, then I filtered the variable with the Where-Object and added two filters, to find two AD groups that the user should be member of.
The problem comes when the user is not in any of those groups (that it could happen because not everyone uses the programs related to those groups), the console prints a ugly error that we don't want the user to see... I tried adding -ErrorAction SilentlyContinue to the Where-Object with no avail, the error is still popping up.
Do you guys have any clue on this?
Here's the code, so you can understand better what I'm trying to suppress:
$gpresult = gpresult /r
$userGroups = ($gpresult | Where-Object -FilterScript {($_ -match 'Group1_*') -or ($_ -match 'Group2_*')} -ErrorAction SilentlyContinue).Trim()
Thanks in advance!
Edit:
Here's the error I get (with 2>$null):

I tried adding -ErrorAction SilentlyContinue to the Where-Object to no avail, the error is still popping up.
The unwanted error happens during the (...).Trim() method call, not in the Where-Object pipeline:
If the pipeline produces no output, the statement is equivalent to $null.Trim(), which predictably causes the following statement-terminating error:
You cannot call a method on a null-valued expression.
Therefore, to avoid this error, you must avoid the .Trim() call if the pipeline produces no output:
$userGroups =
$gpresult |
Where-Object -FilterScript {($_ -match 'Group1_*') -or ($_ -match 'Group2_*')} |
ForEach-Object Trim
Note: The above uses simplified syntax to call .Trim() on each input object from the pipeline; if there is no input object, no call is made, which avoids the error.
The non-simplified equivalent of ForEach-Object Trim is ForEach-Object { $_.Trim() }
You could alternatively use a try { ... } catch { ... } statement to suppress the error (a simplified example: try { $null.Trim() } catch { }), but note that catching statement-terminating errors (exceptions) is comparatively slower than the above approach.

I am not completely sure I understand what you are trying to do but you can separate standard out and standard error streams
For example redirecting stderr to null will completely remove it. If you add this to the end of your command.
2>$null
2 is error stream
If you want to separate them later you should be able to. Because data from stdout will be strings and data from stderr System.Management.Automation.ErrorRecord objects.
$gpresult = gpresult /r
$stderr = $gpresult | ?{ $_ -is [System.Management.Automation.ErrorRecord] }
$stdout = $gpresult | ?{ $_ -isnot [System.Management.Automation.ErrorRecord] }

Related

Is there a way to Expand-Archive without overwriting files?

Is there a way in PowerShell to use Expand-Archive so that files are written where they don't exist, but are not overwritten when they do exist? I can achieve this with -ErrorAction SilentlyContinue, but that ignores things that might be actual errors.
To silence only "file already exists" error messages of Expand-Archive, you can redirect the error stream to the success stream and process error records using ForEach-Object:
Expand-Archive -Path Test.zip -DestinationPath . -EA Continue 2>&1 | ForEach-Object {
if( $_ -is [System.Management.Automation.ErrorRecord] ) {
if( $_.FullyQualifiedErrorId -split ',' -notcontains 'ExpandArchiveFileExists' ) {
Write-Error $_ # output error that is not "file exists"
}
}
else {
$_ # pass success stream through
}
}
-EA Continue (-ErrorAction) overrides the preference variable $ErrorActionPreference to make sure errors are not turned into exceptions (in which case the first error would interrupt the extraction).
2>&1 redirects (merges) the error stream (#2) to the success stream (#1), so both can be processed using ForEach-Object.
$_ -is [System.Management.Automation.ErrorRecord] tests if the current pipeline element is an error record.
When this is the case, we test what kind of error we have, by checking the FullyQualifiedErrorId property of the ErrorRecord (the exception type System.IO.IOException would be too general to test for)
Otherwise it is a message from the success stream, which will be simply passed through.
In case you are wondering how I came up with that FullyQualifiedErrorId thing, I just run Expand-Archive without redirection and called Get-Error afterwards. This outputs all information of the last error record, so I could look up the information to detect the error condition.
An alternative solution, similar to the one suggested by Abraham Zinala, is to unconditionally silence all errors and use -ErrorVariable to collect the errors and shown the relevant ones after the call to Expand-Archive has returned:
$oldErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = 'SilentlyContinue'
$archiveErrors = $null
Expand-Archive -Path Test.zip -DestinationPath . -ErrorVariable archiveErrors
$ErrorActionPreference = $oldErrorActionPreference
$archiveErrors | Sort-Object { $_ | Out-String } -Unique | ForEach-Object {
if( $_ -is [System.Management.Automation.ErrorRecord] ) {
if( $_.FullyQualifiedErrorId -split ',' -notcontains 'ExpandArchiveFileExists' ) {
$_ # output error that is not "file exists"
}
}
}
The errors of Expand-Archive cannot be completely silenced through the -ErrorAction parameter, because some errors (like input file doesn't exist) are detected as part of parameter validation. To really silence all errors, the $ErrorActionPreference variable must be used.
It is important to set the error variable to $null before calling Expand-Archive because the command doesn't reset the variable, when there is no error.
The name of the variable passed to -ErrorVariable must be specified without $.
The Sort-Object -Unique command makes sure we don't show duplicate errors.

Cannot validate argument on parameter 'Property' when running Where-object on an array of simple strings

I guess I'm overlooking something super obvious here... running this code:
$arraytest = #("one", "two", "three")
$arraytest | Where-Object $_ -Like "*hre*" | foreach { Write-Host $_ }
Produces "Where-Object: Cannot validate argument on parameter 'Property'. The argument is null or empty." in pwsh.
I don't understand why. I checked Microsoft docs for pwsh arrays, and also the docs for Where-Object, but no luck.
Following this question I replaced the line with:
$arraytest | Where {$_ -match "hre"} | foreach { Write-Host $_ }
This works. Is there some hidden default parameter I should have referenced in the -Like example? Why does -match work but -like doesn't?
Since PowerShell 3.0, Where-Object has two distinct syntaxes you can use - but you can't mix them the way you're currently trying.
ScriptBlock syntax (or filter script syntax)
This is the "classic" way of using Where-Object, available since PowerShell 2.0 - you supply a scriptblock and Where-Object then uses this block as a predicate for each input item.
It automatically binds the input values to $_ on each iteration so you can do:
... |Where {$_ -match "hre"}
# or
... |Where {$_ -like "*hre*"}
# or
... |Where {$_.Length -gt 3}
As you can see, we have full access to the input value itself via $_, as well as any properties the object might have.
Property syntax
After the release of PowerShell 2.0, many community members started sharing reusable scripts and snippets online, and it became clear that:
Many people were using the same simple form over and over: {$_.<propertyName> -<operator> <someValue>}
The scriptblock syntax was not immediately intuitive (at least not to anyone who didn't have experience with Perl, from which the { $_ } syntax for anonymous sub routines was largely inspired)
For this reason, Where-Object was augmented with a number of parameter sets in version 3.0 that would allow simplification of the most common use case by providing parameters that look like operators:
Where {$_.Length -match "hre"} could then be written as Where Length -match hre
The only limitation with the new syntax is that you must supply a target property to compare against - so you can no longer compare against the intrinsic value of whatever input value would have been bound to $_ in a filter script.
For this reason, you have to stick with the classic scriptblock-based syntax:
$arraytest = #("one", "two", "three")
$arraytest |Where-Object { $_ -like "*hre*" } |ForEach-Object { Write-Host $_ }
In summary:
Property syntax is useful for writing concise filtering statements when you want to evaluate a specific property value on the input objects
ScriptBlock syntax is required whenever you need to refer to the input object itself via $_
Is there ever a circumstance under which ... | Where-Object $_ -Like "*hre*" is a valid and meaninfful pipeline expression?
Yes!
When you're using Where-Object in a scope where the property name you're filtering on has already been bound to $_:
'Length' |ForEach-Object {
Get-ChildItem -File |Where-Object $_ -gt 1KB
}

Why Am I geting an Empty Pipeline Error in this Script

I'm an extremely novice Powershell student who was given the task of getting the following code to work and I keep getting an Empty Pipeline Error at the line remarked 'Gives Empty Pipeline Error'. After quite a few hours of researching this I am still stumped as to what is causing this. The script is supposed to search the Application.evtx log and return any errors from the last 24 hours. I would greatly appreciate any help that could get me pointed in the right direction.
Here's the code:
#look for Errors script
#Creates Function named CheckLogs
Function CheckLogs()
{
# Defines a named parameter $logfile as a string
param ([string]$logfile)
if(!$logfile) {write-host "Usage: ""C:\Windows\System32\winevt\Logs\Application.evtx"""; exit}
# Accesses the file stored in $logfile variable and looks for the string "ERROR"
cat $logfile | Select-string "ERROR" -SimpleMatch |select -expand line |
foreach
{
$_ -match '(.+)\s\[(ERROR)\]\S(.+)'| Out-Null
new-object psobject -Property#{Timestamp=[datetime]$matches[1];Error=$matches[2]}
| #Gives Empty Pipeline Error
where {$_.timestamp -gt (get-date).AddDays(-1)}
$error_time=[datetime]($matches[1])
if ($error_time -gt (Get-Date).AddDays(-1))
{
write-output "CRITICAL: There is an error in the log file $logfile around
$($error_time.ToShortTimeString( ))"; exit(2)
}
}
write-output "OK: There were no errors in the past 24 hours."
}
CheckLogs "C:\Windows\System32\winevt\Logs\Application.evtx" #Function Call
You can't put the pipe | character on a line by itself. You can end a line with | and then continue the pipeline on the next line though.
This should work:
new-object psobject -Property#{Timestamp=[datetime]$matches[1];Error=$matches[2]} |
where {$_.timestamp -gt (get-date).AddDays(-1)}

Remote Powershell to retrieve specific registry value from lots of servers

I have the following..
$output = #()
$servers =Get-Content "C:\Windows\System32\List3.txt"
foreach ($server in $servers)
{
trap [Exception] {continue}
Import-Module PSRemoteRegistry
$key="SOFTWARE\Microsoft\'Microsoft Antimalware'\'Signature Updates'"
$regkey=Get-RegBinary -ComputerName $server -Key $Key -Value SignatuesLastUpdated
#$regkey=(Get-Item HKLM:\SOFTWARE\Microsoft\'Microsoft Antimalware'\'Signature Updates').getValue('SignaturesLastUpdated')
#$regkey=[datetime]::ParseExact("01/02/03", "dd/MM/yy", $null) | Export-csv -path c:\temp\avinfo.csv -append
#$regkey
}
$output | Select $server , $Regkey | Export-Csv c:\temp\avinfo.csv -NoTypeInformation
I think it's pretty close but doesn't work as needed - can anyone tell me what I am doing wrong here - been reading a lot and managed to get this far, just need the help to finalise.
Thanks
Ok... so there is alot that needed to be changed to get this to work. I will update the answer frequently after this is posted.
$servers = Get-Content "C:\Windows\System32\List3.txt"
$key="SOFTWARE\Microsoft\Microsoft Antimalware\Signature Updates"
$servers | ForEach-Object{
$server = $_
Try{
Get-RegBinary -ComputerName $server -Key $Key -Value SignatuesLastUpdated -ErrorAction Stop
} Catch [exception]{
[pscustomobject]#{
ComputerName = $server
Data = "Unable to retrieve data"
}
}
} | Select ComputerName,#{Label=$value;Expression={If(!($_.Data -is [string])){[System.Text.Encoding]::Ascii.GetBytes($_.data)}Else{$_.Data}}} | Export-Csv c:\temp\avinfo.csv -NoTypeInformation
What the above code will do is more in line with your intentions. Take the list and for each item get the key data from that server. If there is an issue getting that data then we output a custom object stating that so we can tell in the output if there was an issue. The part that is up in the air is how you want to export the binary data to file. As it stands it should create a space delimited string of the bytes.
The issues that you did have that should be highlighted are
No need to import the module for every server. Moved that call out of the loop
You have declared the variable $output but do not populate it during your loop process. This is important for the foreach construct. You were, in the end, sending and empty array to you csv. My answer does not need it as it just uses standard output.
As #Meatspace pointed out you had a typo here: SignatuesLastUpdated
Get-RegBinary does not by default create terminating errors which are needed by try/catch blocks. Added -ErrorAction Stop. Don't think your code trap [Exception] {continue} would have caught anything.
The single quotes you have in your $key might have prevented the path from being parsed. You were trying to escape spaces and just need to enclose the whole string in a set of quotes to achieve that.
While Select can use variables they are there, in a basic form, to select property names. In short what you had was wrong.

PowerShell query: how to process a file

I often want to process a file resulting in a modified output file. I can't seem to find the PowerShell way to do this - it always ends up looking like code in a one-liner, IYSWIM. This is an example.
I have an LMHOSTS file. Don't ask why! I want to get a new LMHOSTS file that only contains servers that respond to pings. I also want to pass through any comment lines.
Comment lines begin with a #.
Data lines are something like this (space-separated):
10.123.177.1 SVRDS1 #PRE #DOM:DOM1
or this:
10.241.177.30 SVRDS30 #PRE
This is what I've got (with thanks to Luke for help with Ping-Host):
gc 'C:\work\lmhosts.txt' | % { if ($_ -like '#*') { $_ | out-file 'C:\work\lmhosts2.txt' -append } else { if ($(Ping-Host $_.Substring(0, $_.indexof(' ')) -count 1 -timeout 10).received -eq 1) { $_ | out-file 'C:\work\lmhosts2.txt' -append } } }
It works but it's not nice. I've taken a programmer's approach but done it as a one-liner. Is there a better way of doing it, that's more 'shell' and less 'code'?
In this particular example, you are filtering the contents of 1 file and outputting it to another. Where-Object is usually a good choice for this. You can then simplify your one-liner a bit:
gc 'C:\work\lmhosts.txt' |
?{ ($_ -like '#*') -or
($(Ping-Host $_.Split(' ')[0]) -count 1 -timeout 10).received -eq 1) } >
'C:\work\lmhosts2.txt'
Some notes:
"?" is an alias for Where-Object
The -or operator will short circuit, that is, if the first operand results in True, the second will not bother executing.
You can use the redirection operator ">" instead of "| Out-File".
I replaced Substring with Split, it seemed slightly simpler, not sure if works in general with your input.
Whitespace is your friend! In Powershell scripts I write that aren't immediately thrown away, almost every '|' is followed by a newline + indent.