Where-Object Error When Passing Get-Content as Variable - powershell

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 | ...

Related

I need to pass a line number from an object

>$search="<table id="
$linenumber= Get-Content ".\145039.html" | select-string $search | Select-Object LineNumber
$search="</table>"
$linenumber2= Get-Content ".\145039.html" | select-string $search | Select-Object LineNumber
#$linenumber2
# the list of line numbers to fetch
$linesToFetch = $linenumber[2]..$linenumber2[2]
$currentLine = 1
$result = switch -File ".\145039.html" {
default { if ($linesToFetch -contains $currentLine++) { $_ }}
}
# write to file and also display on screen by using -PassThru
$result | Set-Content -Path ".\excerpt.html" -PassThru
Cannot convert the "#{LineNumber=6189}" value of type "Selected.Microsoft.PowerShell.Commands.MatchInfo" to type "System.Int32".
At line:10 char:1
+ $linesToFetch = $linenumber[2]..$linenumber2[2]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [], RuntimeException
+ FullyQualifiedErrorId : ConvertToFinalInvalidCastException
$linenumber and $linenumber2 return values like below but I just need to get the number not the column header.
LineNumber
----------
6015
Also, the final version needs to loop through all the html files in a directory not just one static file.
Sorry, there is probably a better way to do this but not sure how.
Thanks in advance!
Did a lot of googling but could not find the right solution.
Updated code:
$search1="disconnect-status"
$linenumber1= Get-Content ".\145039.html" | select-string
$search1
| Select-Object -ExpandProperty LineNumber
$search2="</table>"
$linenumber2= Get-Content ".\145039.html" | select-string
$search2 | Select-Object -ExpandProperty LineNumber
# the list of line numbers to fetch
$linesToFetch = $linenumber1[3]..$linenumber2[1]
$currentLine = 1
$result = switch -File ".\145039.html" {
default { if ($linesToFetch -contains $currentLine++) { $_ }}
}
# write to file and also display on screen by using -PassThru
$result | Set-Content -Path ".\excerpt.html" -PassThru
_____________________________________________________________
Thank you # mklement0
This now works for one file at a time now I need it go select text from all the HTML files in the directory.
Your immediate problem is that you need to change Select-Object LineNumber (which, due to positional parameter binding, is equivalent to Select-Object -Property LineNumber) to Select-Object -ExpandProperty LineNumber.
That is, you must use Select-Object's -ExpandProperty parameter in order to only get the values of the input objects' .LineNumber properties - see this post.
That said, your approach can be optimized in a number of ways, allowing you to make do with only a switch statement:
$output = $false; $openTagCount = 0
$result =
switch -File .\145039.html {
'<table id=' {
if (++$openTagCount -eq 3) { $output = $true } # 3rd block found
continue
}
'</table>' {
if ($output) { break } # end of 3rd block -> exit
continue
}
default {
if ($output) { $_ } # inside 3rd block -> output line
}
}
Note: This extracts the lines inside the third <table> element that has an id attribute, as implied by your original solution attempt; the solution you later edited into the question works differently.
Taking a step back:
It looks like your input is HTML, so you're usually better off using HTML parsing to handle your input:
In Windows PowerShell you may be able to use Invoke-WebRequest relying on the Internet Explorer engine if present (it isn't anymore by default in recent Windows versions).
In recent versions of Windows and in PowerShell (Core) (v6+), you'll either need New-Object -Com HTMLFile - see this answer - or a third-party solution such as such as the PowerHTML module that wraps the HTML Agility Pack - see this answer.

Getting a specific process id from a powershell output

Hi I'm new to powershell scripting and I would like to retrieve a specific process id based on the file's hash. However I can only get either a table with the hash value or a table with the id,process name and path
$ps = Get-Process | Select-Object -Property Id,ProcessName,Path
$hashlist = Get-FileHash(Get-Process|Select-Object -ExpandProperty Path) -Algorithm MD5
Is it possible for me to merge the two tables together so that I can get a view of Id,ProcessName and Hash using the path to link them together?
EDIT: totally different approach due to new information from comment
I don't think it is so easy to identify malware with a file MD5 hash.
Modern AntiVirusSoftware uses heuristics to overcome the problem of mean malware which includes random data and also obfuscates it's origin.
## Q:\Test\2018\11\11\SO_53247430.ps1
# random hex string replace with your malware signature
$MalwareMD5Hash = 'D52C11B7E076FCE593288439ABA0F6D4'
Get-Process | Where-Object Path | Select-Object ID,Path | Group-Object Path | ForEach-Object {
if ($MalwareMD5Hash -eq (Get-FileHash $_.Name -Alg MD5).Hash){
##iterate group to kill all processes matching
ForEach ($PID in $_.Group.ID){
Stop-Process -ID $PID -Force -WhatIF
}
}
$_.Name | Remove-Item -Force -WhatIf # to delete the physical file.
}
As I suggested in my comment:
$HashList = [ordered]#{}
Get-Process |Where-Object Path | Select-Object Path |Sort-Object Path -Unique | ForEach-Object {
$HashList[$_.Path]=(Get-FileHash $_.Path -Alg MD5).Hash
## or the reverse, the hash as key and the path as value
# $HashList[(Get-FileHash $_.Path -Alg MD5).Hash]=$_.Path
}
$Hashlist | Format-List
Shorted sample output
Name : C:\Program Files\Mozilla Firefox\firefox.exe
Value : BFE829AB5A4B729EE4565700FC8853DA
Name : C:\WINDOWS\Explorer.EXE
Value : E4A81EDDFF8B844D85C8B45354E4144E
Name : C:\WINDOWS\system32\conhost.exe
Value : EA777DEEA782E8B4D7C7C33BBF8A4496
Name : C:\WINDOWS\system32\DllHost.exe
Value : 2528137C6745C4EADD87817A1909677E
> $hashlist['C:\WINDOWS\Explorer.EXE']
E4A81EDDFF8B844D85C8B45354E4144E
Or with the reversed list
> $hashlist['E4A81EDDFF8B844D85C8B45354E4144E']
C:\WINDOWS\Explorer.EXE

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]}}

Using Powershell to compare two files and then output only the different string names

So I am a complete beginner at Powershell but need to write a script that will take a file, compare it against another file, and tell me what strings are different in the first compared to the second. I have had a go at this but I am struggling with the outputs as my script will currently only tell me on which line things are different, but it also seems to count lines that are empty too.
To give some context for what I am trying to achieve, I would like to have a static file of known good Windows processes ($Authorized) and I want my script to pull a list of current running processes, filter by the process name column so to just pull the process name strings, then match anything over 1 character, sort the file by unique values and then compare it against $Authorized, plus finally either outputting the different process strings found in $Processes (to the ISE Output Pane) or just to output the different process names to a file.
I have spent today attempting the following in Powershell ISE and also Googling around to try and find solutions. I heard 'fc' is a better choice instead of Compare-Object but I could not get that to work. I have thus far managed to get it to work but the final part where it compares the two files it seems to compare line by line, for which would always give me false positives as the line position of the process names in the file supplied would change, furthermore I only want to see the changed process names, and not the line numbers which it is reporting ("The process at line 34 is an outlier" is what currently gets outputted).
I hope this makes sense, and any help on this would be very much appreciated.
Get-Process | Format-Table -Wrap -Autosize -Property ProcessName | Outfile c:\users\me\Desktop\Processes.txt
$Processes = 'c:\Users\me\Desktop\Processes.txt'
$Output_file = 'c:\Users\me\Desktop\Extracted.txt'
$Sorted = 'c:\Users\me\Desktop\Sorted.txt'
$Authorized = 'c:\Users\me\Desktop\Authorized.txt'
$regex = '.{1,}'
select-string -Path $Processes -Pattern $regex |% { $_.Matches } |% { $_.Value } > $Output_file
Get-Content $Output_file | Sort-Object -Unique > $Sorted
$dif = Compare-Object -ReferenceObject $(Get-Content $Sorted) -DifferenceObject $(get-content $Authorized) -IncludeEqual
$lineNumber = 1
foreach ($difference in $dif)
{
if ($difference.SideIndicator -ne "==")
{
Write-Output "The Process at Line $linenumber is an Outlier"
}
$lineNumber ++
}
Remove-Item c:\Users\me\Desktop\Processes.txt
Remove-Item c:\Users\me\Desktop\Extracted.txt
Write-Output "The Results are Stored in $Sorted"
From the length and complexity of your script, I feel like I'm missing something, but your description seems clear
Running process names:
$ProcessNames = #(Get-Process | Select-Object -ExpandProperty Name)
.. which aren't blank: $ProcessNames = $ProcessNames | Where-Object {$_ -ne ''}
List of authorised names from a file:
$AuthorizedNames = Get-Content 'c:\Users\me\Desktop\Authorized.txt'
Compare:
$UnAuthorizedNames = $ProcessNames | Where-Object { $_ -notin $AuthorizedNames }
optional output to file:
$UnAuthorizedNames | Set-Content out.txt
or in the shell:
#(gps).Name -ne '' |? { $_ -notin (gc authorized.txt) } | sc out.txt
1 2 3 4 5 6 7 8
1. #() forces something to be an array, even if it only returns one thing
2. gps is a default alias of Get-Process
3. using .Property on an array takes that property value from every item in the array
4. using an operator on an array filters the array by whether the items pass the test
5. ? is an alias of Where-Object
6. -notin tests if one item is not in a collection
7. gc is an alias of Get-Content
8. sc is an alias of Set-Content
You should use Set-Content instead of Out-File and > because it handles character encoding nicely, and they don't. And because Get-Content/Set-Content sounds like a memorable matched pair, and Get-Content/Out-File doesn't.

Powershell - Strange output when using Get-ChildItem to search within files

I have a problem I am hoping someone could help with....
I have a powershell script containing the lines shown below:
$output = Get-ChildItem -path $target -recurse | Select-String -pattern hello | group path | select name
Write-Output "Output from the string match is $output"
The error I am getting:
Output from the string match Microsoft.Powershell.Commands.GroupInfo Microsoft.Powershell.Commands.GroupInfo
When I run this command on it's own (ie not within a script) it works perfectly and returns the two files in that location that contains the word "hello".
It appears that it knows there are two things it has found because it prints the "Microsoft.Powershell.Commands.GroupInfo" text twice (as shown above in the error). But why is it printing this and not the path to the files as it should do?
There must be something obvious I am overlooking but I dont know what.
Your help is much appreciated, thanks
The reason you're seeing this is because $output is an array of Selected.Microsoft.PowerShell.Commands.GroupInfo objects -- the objects returned by Group-Object when passed to Select-Object (without Select-Object they would just be Microsoft.PowerShell.Commands.GroupInfo objects instead). You can confirm the type of objects in $ouput by running:
$output | Get-Member
Check the TypeName that is displayed at the top of the output.
When you run these commands interactively in the console, you are seeing the paths because PowerShell knows how to display GroupInfo objects in the console so that they are human-readable. Note that when you just call $output in the console, you see a "Name" header underlined with dash characters -- this is PowerShell interpreting the GroupInfo object you gave it and displaying the Name property for you in the console.
The problem occurs when you try to output the $output array inside a string. Then PowerShell is not able to use its more advanced formatting logic and instead merely tries to convert the object to a string to insert into your string. When it does that, it doesn't have enough logic to know that what you really want to appear in your string is the Name property of these GroupInfo objects, so instead if just prints out a string with the type name of each of the objects in the $output array. So that's why you see the type name twice.
The simple solution to this problem is the -ExpandProperty parameter for Select-Object. This does what it says -- it expands the property you asked for with Select-Object and returns just that property, not the parent object. So the Name property of a GroupInfo object is a string. If you call Select-Object Name, you get a GroupInfo object with the Name property. If you call Select-Object -ExpandProperty Name, you get just the Name property as a String object. Which is what I expect that you want in this case.
So try this instead:
$output = Get-ChildItem -path $target -recurse | Select-String -pattern hello | group path | select -ExpandProperty name
A foreach would be appropriate here I believe. Try this:
$output = Get-ChildItem -path $target -recurse | where {$_.name -like "*hello*"} | select name
foreach ($file in $output) {
write-host $file.name
}
Or this:
$output = Get-ChildItem -path $target -recurse | select-string -pattern "hello" | select name
foreach ($file in $output) {
write-output $file.name
}