ErrorVariable output not as expected - powershell

First, here is my code:
$InputPath = "\\Really\long\path\etc"
Get-ChildItem -Path $InputPath -Recurse -Force -ErrorVariable ErrorPath
$ErrorPath | select TargetObject
The problem I am facing is with the ErrorVariable parameter. Paths that are too long for Get-ChildItem to process are stored there (typically around 250+ characters). When I pipe $ErrorPath to select, the output looks like this:
TargetObject : \\Really\long\path\etc\1
TargetObject : \\Really\long\path\etc\2
TargetObject : \\Really\long\path\etc\3
However, if I run that last line again (either by running selection or by manually typing it in), the output changes to this:
TargetObject
------------
\\Really\long\path\etc\1
\\Really\long\path\etc\2
\\Really\long\path\etc\3
I am at a loss as to how to explain this. I prefer the second output, but I don't know why it is different from the first to the second time. Any ideas?

When there are multiple types of objects in a pipeline the first object type determines the formatting for the rest of the objects. If you ran your commands individually on the command prompt you wouldn't see this. Each line starts a new pipeline. However when you run these commands in a script, the whole script is executed as a pipeline. To work around this use Out-Default e.g.:
$InputPath = "\\Really\long\path\etc"
Get-ChildItem $InputPath -Recurse -Force -ErrorVariable ErrorPath
$ErrorPath | select TargetObject | Out-Default

The former output is in list format, the latter in table format. You can enforce one or the other by piping your data into the Format-List or Format-Table cmdlet respectively:
PS C:\> $ErrorPath | select TargetObject | Format-List
TargetObject : \\Really\long\path\etc\1
TargetObject : \\Really\long\path\etc\2
TargetObject : \\Really\long\path\etc\3
PS C:\> $ErrorPath | select TargetObject | Format-Table
TargetObject
------------
\\Really\long\path\etc\1
\\Really\long\path\etc\2
\\Really\long\path\etc\3

Related

Where-Object Error When Passing Get-Content as Variable

First, my PS knowledge is very basic, so know that up front.
I'm working on a basic script to search EventIDs in archived .evtx files and kick out "reports". The Where-Object queries are in .txt files stored in .\AuditEvents\ folder. I'm trying to do a ForEach on the .txt files and pass each query to Get-WinEvent.
Here's an example of how the queries appear in the .txt files:
{($_.ID -eq "11")}
The script is:
$ae = Get-ChildItem .\AuditEvents\
ForEach ($f in $ae) {
$qs = Get-Content -Path .\AuditEvents\$f
Get-WinEvent -Path .\AuditReview\*.evtx -MaxEvents 500 | Select-Object TimeCreated, ID, LogName, MachineName, ProviderName, LevelDisplayName, Message | Where-Object $qs | Out-GridView -Title $f.Name
}
This is the error:
Where-Object : Cannot bind argument to parameter 'FilterScript' because it is null.
At C:\Users\######\Desktop\PSAuditReduction\PSAuditReduction.ps1:6 char:177
+ ... e, ProviderName, LevelDisplayName, Message | Where-Object $qs | Out-G ...
+ ~~~
+ CategoryInfo : InvalidData: (:) [Where-Object], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.WhereObjectCommand
Your symptom implies that $qs is $null, which in turn implies that file .\AuditEvents\$f is empty.
However, even if it had content, you couldn't pass the resulting string as-is to the (positionally implied) -FilterScript parameter of Where-Object requires a script block ({ ... }).
You must create a script block from the string explicitly, using [scriptblock]::Create().
A simplified example:
# Simulated input using a literal string instead of file input via Get-Content
$qs = '{ 0 -eq $_ % 2 }' # Sample filter: return $true for even numbers.
# Remove the enclosing { and }, as they are NOT part of the code itself
# (they are only needed to define script-block *literals* in source code).
# NOTE: If you control the query files, you can simplify them
# by omitting { and } to begin with, which makes this
# -replace operation unnecessary.
$qs = $qs.Trim() -replace '^\{(.+)\}$', '$1'
# Construct a script block from the string and pass it to Where-Object
1..4 | Where-Object ([scriptblock]::Create($qs)) # -> 2, 4
Note:
Your code assumes that each .\AuditEvents\$f file contains just one line, and that that line contains valid PowerShell source code suitable for use a Where-Object filter.
Generally, be sure to only load strings that you'll execute as code from sources you trust.
Taking a step back:
As Abraham Zinala points out, a much faster way to filter event-log entries is by using Get-WinEvent's -FilterHashtable parameter.
This allows you to save hastable literals in your query files, which you can read directly into a hashtable with Import-PowerShellDataFile:
# Create a file with a sample filter.
'#{Path=".\AuditEvents\.*evtx";ID=11}' > sample.txt
# Read the file into a hashtable...
$hash = Import-PowerShellDataFile sample.txt
# ... and pass it to Get-WinEvent
Get-WinEvent -MaxEvents 500 -FilterHashtable $hash | ...

How to use previous command output as a parameter in Powershell?

I need to write a Powershell one-liner to do a complex task. I need it to be strictly one line because I want to run it in a Go and Python script. The task requires taking the output of the first command and use it as a parameter in the second command.
I thought it was a simple task but I am struggling with it quite a bit. For example, the below command does not work:
$obj = Get-Item . | Get-Item $obj.Root | Format-List *
Get-Item : The input object cannot be bound to any parameters for the command either because the command does not take
pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
At line:1 char:21
+ $obj = Get-Item . | Get-Item $obj.Root | Format-List *
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (C:\Users\fhcat:PSObject) [Get-Item], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,Microsoft.PowerShell.Commands.GetItemCommand
What am I doing wrong?
Strictly one line sounds like a personal preference rather than a technical requirement.
However, you can end a statement with a semicolon or process the input via ForEach-Object
$obj = Get-Item .; Get-Item $obj.Root | Format-List *
or
Get-Item . | ForEach-Object {Get-Item $_.Root} | Format-List *

Joining value from Context parameter to Line property from Select-String output

I'm trying to compile log errors from multiple files in a single directory. The error messages are included over the span of two lines. I would like to concatenate both lines into a single line/object and then export all errors into a a neat csv.
I'm attempting to accomplish this with the Select-String utility, and the -Context parameter. Prior to piping the results through the Select-Object utility, everything's Kosher. However, Once I pipe the results through Select-Object or Export-CSV, the -Context line is lost.
$trigger = 'ERROR'
$folderPath = 'C:\Users\test\Desktop\testpath'
$logFiles = gci -Path $folderPath -Filter *.txt -File
$logFiles | Select-String -Pattern $trigger -CaseSensitive -SimpleMatch -Context 0,1 | Select-Object LineNumber, Line, Filename |
Export-Csv -Path .\$(Get-Date -Format yyyymmddhhmmss).csv -Encoding UTF8 -NoTypeInformation
Omitting the Select-Object and Export-Csv Cmdlets yields the desired, raw, results with the friendly right angle bracket '>' (ASCII 62). The raw results can even be exported via the Out-File Cmdlet, no problem.
However, what I would like to do, is combine the Pattern line with the Context line, creating a single object, which would eventually be output as a csv for further analysis.
I would like apologize if this question seems trivial. I've scoured resources trying to figure this out and unfortunately haven't been able to. Thanks in advance!
Pipe select-string through fl * to see what the properties are.
$a = ls log | select-string error -context 0,1
$a | fl *
IgnoreCase : True
LineNumber : 2
Line : error
Filename : log
Path : /Users/js/log
Pattern : error
Context : Microsoft.PowerShell.Commands.MatchInfoContext
Matches : {0}
$a.context
PreContext PostContext DisplayPreContext DisplayPostContext
---------- ----------- ----------------- ------------------
{} {after } {} {after }
This worked for me:
ls log | select-string error -context 0,1 | select linenumber, line,
#{n='PostContext'; e={$_.context.postcontext}}, filename
LineNumber Line PostContext Filename
---------- ---- ----------- --------
2 error after log

What's wrong with my recursive delete?

Get-ChildItem -recurse | ? {$_.Extension -eq ".obj" } | %{del $_}
~~~~~~
CategoryInfo : ObjectNotFound: (C:\Temp\compilerLimits\template.obj:String) [Remove-Item], ItemNotFoundException
FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand
Trying to recursively delete all .obj files;
instead I get this.
Try it like this:
Get-ChildItem -recurse *.obj | Remove-Item
In the case of | %{del $_}, $_ is a System.IO.FileInfo object and when PowerShell uses this for the LiteralPath parameter, it sees that the LiteralPath parameter takes a string. The conversion of FileInfo to a string seems to use the FileInfo.ToString() method which in some cases (like for subdirs) doesn't include the full path - just the filename. That will cause the error your are seeing. When you pipe in the FileInfo object, pipeline argument binding rules are used. The LiteralPath parameter has an alias called PSPath. This property is added to each FileInfo object by PowerShell's type system. You can see this with Get-ChildItem *.obj | Get-Member. And since the LiteralPath parameter has ValueFromPipelineByPropertyName set to true, PowerShell will get the argument value from the object's PSPath property.
You can read more about this in Item 8 of my Effective PowerShell ebook.

Why don't errors get returned when calling properties that don't exist?

Given the following snippet
$drives = Get-PSDrive
foreach($drive in $drives)
{
Write-Host $drive.Name "`t" $drive.Root
Write-Host " - " $drive.Free "`t" $drive.PropertyDoesntExist
}
The drive.PropertyDoesntExist property doesn't... erm... exist so I would expect an error to be thrown but instead it returns a null.
How can I get errors or exceptions?
EDIT - Me bad - I asked 2 questions in one so I moved one into a separate question.
The NextHop Blog provides a good solution to this problem. It doesn't give you an error, but instead a boolean. You can use Get-Member to get a collection of all of the real properties of the object's type and then match for your desired property.
Here's an example for strings:
PS C:\> $test = "I'm a string."
PS C:\> ($test | Get-Member | Select-Object -ExpandProperty Name) -contains "Trim"
True
PS C:\> ($test | Get-Member | Select-Object -ExpandProperty Name) -contains "Pigs"
False
If you explicitly want an error, you may want to look into Set-Strictmode as Set-StrictMode -version 2 to trap non-existent properties. You can easily turn it off when you're done with it, too:
PS C:\> Set-StrictMode -version 2
PS C:\> "test".Pigs
Property 'Pigs' cannot be found on this object. Make sure that it exists.
At line:1 char:8
+ "test". <<<< Pigs
+ CategoryInfo : InvalidOperation: (.:OperatorToken) [], RuntimeException
+ FullyQualifiedErrorId : PropertyNotFoundStrict
PS C:\> Set-StrictMode -off