Powershell - Match one or another - powershell

I have a log file that has user login/logout events, formatted like this:
11/29/19 10:46:41.976 S I [T-22156] - User Logout: Bob Jones (Home)
11/29/19 10:46:51.293 S I [T-22156] - HTTP UserMgr: Start User login notify for user: Jane Smith (Studio)
I am calling the above text from my log file using Get-Content and saving it to $testtext.
In Expresso, I built the following regex, and when I run it, Expresso matches my DateTime, Status, and Name without any errors on the above text.
$regex = [regex]::new('(?<DateTime>\d{2}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2})(?:.*)(?<Status>User\slog\w{2,3})(?:.*?)(?<Name>[A-Z][a-zA-Z]+\s[A-Z][a-zA-Z]+\s\([A-Z]+\))')
But when I run the following in Powershell, it only outputs the Login events
$parsed = foreach ($text in $testtext) {
$match = $regex.Match($text)
[PSCustomObject][ordered]#{
Date = $match.Groups["DateTime"].Value
Status = $match.Groups["Status"].Value
Name = $match.Groups["Name"].Value
}
}
$parsed | Out-GridView
How can I have my regex look at each line and fine both matches?

the following is what i meant by "two steps instead of one massive regex". [grin]
what it does ...
fakes reading in text file
replace that entire section with a Get-Content call to load your text file as an array of lines of text.
iterates thru the resulting collection of lines
tests for either login or logout
applies a different regex for each type of line
making a regex for both lines is both more complex and somewhat beyond my skill level. [grin]
creates a PSCO for each set of matches
sends the PSCO out to the $Results collection
displays that collection
at that point, you can export to CSV fairly simply.
the code ...
# fake reading in a plain text file
# in real life, use Get-Content
$InStuff = #'
11/29/19 10:46:41.976 S I [T-22156] - User Logout: Bob Jones (Home)
11/29/19 10:46:51.293 S I [T-22156] - HTTP UserMgr: Start User login notify for user: Jane Smith (Studio)
'# -split [System.Environment]::NewLine
$Results = foreach ($IS_Item in $InStuff)
{
if ($IS_Item -match 'login')
{
$Null = $IS_Item -match '^(.*\s.*) S I .* user (.*) notify for user: (.*)$'
[PSCustomObject]#{
TimeStamp = $Matches[1]
Type = $Matches[2]
User = $Matches[3]
}
}
if ($IS_Item -match 'logout')
{
$Null = $IS_Item -match '^(.*\s.*) S .* User (.*): (.*)$'
[PSCustomObject]#{
TimeStamp = $Matches[1]
Type = $Matches[2]
User = $Matches[3]
}
}
}
$Results
output ...
TimeStamp Type User
--------- ---- ----
11/29/19 10:46:41.976 Logout Bob Jones (Home)
11/29/19 10:46:51.293 login Jane Smith (Studio)

I maybe understanding it wrong. But it looks like the contents are only 1 object. Then when you do a foreach it only checks the one object.
What I would check...
What is the value of $testtext.count? You want it to match the number of lines, not be 1.
If it is 1, then you need to look at splitting each line to be treated as an object.

Related

Insert blank columns into csv with Powershell

In my script, I am building a custom Powershell object which will be sent to Export-Csv. The receiving party has required that I include some blank columns (no data and no header) and I have no idea how to do that.
If the object looks like this:
$obj = [PSCustomObject][ordered]#{
EMPLOYER_EIN = '123456'
ACTION_CODE = 1
LAST_NAME = Smith
FIRST_NAME = John
MIDDLE_INITIAL = $null
EMPLOYEE_SSN = '111-11-1111'
}
How can I have the resulting .csv file's first row look like this:
EMPLOYER_EIN,ACTION_CODE,,LAST_NAME,FIRST_NAME,MIDDLE_INITIAL,,EMPLOYEE_SSN
Put another way, after I run Export-Csv, I want the file to look like this when opened in Excel:
EMPLOYER_EIN
ACTION_CODE
LAST_NAME
FIRST_NAME
MIDDLE_INITIAL
EMPLOYEE_SSN
123456
1
Smith
John
111-11-1111
Note the extra columns between action_code/last_name and middle_initial/employee_ssn. I am using PS 5.1 but could use 7 if necessary.
As a test, I created a CSV test.csv with fields A,B, and C, and put a couple of lines of values:
"A","B","C"
1,2,3
4,5,6
I then executed the sequence of commands
Import-CSV -path Test.csv | Select-Object -Prop A," ",B,C | Export-CSV -Path test2.csv
and looked at the resultant test2.csv, which contained
#TYPE Selected.System.Management.Automation.PSCustomObject
"A"," ","B","C"
"1",,"2","3"
"4",,"5","6"
I believe that this is going to be the closest you'll get without manually processing the CSV as a text file.
This is essentially what Santiago Squarzon was suggesting in the comments.
If you need multiple "blank" columns, each one will have to have a header with a different non-zero number of spaces.
I suggest:
constructing the object with blank dummy properties with a shared name prefix, such as BLANK_, followed by a sequence number (the property names must be unique)
initially piping to ConvertTo-Csv, which allows use of a -replace operation to replace the dummy property names with empty strings in the first output line (the header line).
the result - which already is in CSV format - can then be saved to a CSV file with Set-Content.
$obj = [PSCustomObject] #{
EMPLOYER_EIN = '123456'
ACTION_CODE = 1
BLANK_1 = $null # first dummy property
LAST_NAME = 'Smith'
FIRST_NAME = 'John'
MIDDLE_INITIAL = $null
BLANK_2 = $null # second dummy property
EMPLOYEE_SSN = '111-11-1111'
}
$first = $true
$obj |
ConvertTo-Csv |
ForEach-Object {
if ($first) { # header row: replace dummy property names with empty string
$first = $false
$_ -replace '\bBLANK_\d+'
}
else { # data row: pass through
$_
}
} # pipe to Set-Content as needed.
Output (note the blank column names after ACTION CODE and MIDDLE_INITIAL):
"EMPLOYER_EIN","ACTION_CODE","","LAST_NAME","FIRST_NAME","MIDDLE_INITIAL","","EMPLOYEE_SSN"
"123456","1",,"Smith","John",,,"111-11-1111"

Parsing HTML with <DIV> class to variable

I am trying to parse a server monitoring page which doesnt have any class name . The HTML file looks like this
<div style="float:left;margin-right:50px"><div>Server:VIP Owner</div><div>Server Role:ACTIVE</div><div>Server State:AVAILABLE</div><div>Network State:GY</div>
how do i parse this html content to a variable like
$Server VIP Owner
$Server_Role Active
$Server_State Available
Since there is no class name.. i am struggling to get this extracted.
$htmlcontent.ParsedHtml.getElementsByTagName('div') | ForEach-Object {
>> New-Variable -Name $_.className -Value $_.textContent
While you are only showing us a very small part of the HTML, it is very likely there are more <div> tags in there.
Without an id property or anything else that uniquely identifies the div you are after, you can use a Where-Object clause to find the part you are looking for.
Try
$div = ($htmlcontent.ParsedHtml.getElementsByTagName('div') | Where-Object { $_.InnerHTML -like '<div>Server Name:*' }).outerText
# if you're on PowerShell version < 7.1, you need to replace the (first) colons into equal signs
$result = $div -replace '(?<!:.*):', '=' | ConvertFrom-StringData
# for PowerShell 7.1, you can use the `-Delimiter` parameter
#$result = $div | ConvertFrom-StringData -Delimiter ':'
The result is a Hashtable like this:
Name Value
---- -----
Server Name VIP Owner
Server State AVAILABLE
Server Role ACTIVE
Network State GY
Of course, if there are more of these in the report, you'll have to loop over divs with something like this:
$result = ($htmlcontent.ParsedHtml.getElementsByTagName('div') | Where-Object { $_.InnerHTML -like '<div>Server Name:*' }) | Foreach-Object {
$_.outerText -replace '(?<!:.*):', '=' | ConvertFrom-StringData
}
Ok, so the original question did not show what we are dealing with..
Apparently, your HTML contains divs like this:
<div>=======================================</div>
<div>Service Name:MysqlReplica</div>
<div>Service Status:RUNNING</div>
<div>Remarks:Change role completed in 1 ms</div>
<div>=======================================</div>
<div>Service Name:OCCAS</div>
<div>Service Status:RUNNING</div>
<div>Remarks:Change role completed in 30280 ms</div>
To deal with blocks like that, you need a whole different approach:
# create a List object to store the results
$result = [System.Collections.Generic.List[object]]::new()
# create a temporary ordered dictionary to build the resulting items
$svcHash = [ordered]#{}
foreach ($div in $htmlcontent.ParsedHtml.getElementsByTagName('div')) {
switch -Regex ($div.InnerText) {
'^=+' {
if ($svcHash.Count) {
# add the completed object to the list
$result.Add([PsCustomObject]$svcHash)
$svcHash = [ordered]#{}
}
}
'^(Service .+|Remarks):' {
# split into the property Name and its value
$name, $value = ($_ -split ':',2).Trim()
$svcHash[$name] = $value
}
}
}
if ($svcHash.Count) {
# if we have a final service block filled. This happens when no closing
# <div>=======================================</div>
# was found in the HTML, we need to add that to our final array of PSObjects
$result.Add([PsCustomObject]$svcHash)
}
# output on screen
$result | Format-Table -AutoSize
# output to CSV file
$result | Export-Csv -Path 'X:\services.csv' -NoTypeInformation
Output on screen using the above example:
Service Name Service Status Remarks
------------ -------------- -------
MysqlReplica RUNNING Change role completed in 1 ms
OCCAS RUNNING Change role completed in 30280 ms

How to filter for specific words using power shell

I am trying to figure out who has been printing from a 2008 print server. I generated a log file from server manager and now have this information in a csv file. My goal is to parse through this info and export it to a new csv to find out the only the associates user ID, computer host name and printer name, all included in the csv log file so I can determine who is printing from this server and make sure I can map them to our new print server. The csv consists of one column of data which has a pattern.
Each row in the csv follows the below pattern, but the wording is different for each row since the job name is longer/shorter or other bits of information that I don't want.
The overall pattern is:
Document #, job name owned by **user ID** on **computer host name** was printed on **printer name** through port ********
More information I don't want
My problem is that I can't hard code something like ignore the first 5 words of each row then the 6th word would be the user ID etc, since the format is different on each row.
What is the best way for me to ignore all words up until either the phrase "owned by", or even better, user ID, save that to a new csv in, then do the same thing for the computer host name and printer name?
This could be done easily enough using Regular Expression matching. Regular Expressions use pattern matching, so you could do something like:
Get-Content LogFile.csv | Where{$_ -match "^(.*?),.+? owned by (.+?) on (.+?) was printed on (.+?) through port (.+)"} | ForEach{
[PSCustomObject]#{
JobNumber=$Matches[1]
UserId=$Matches[2]
ComputerName=$Matches[3]
PrinterName=$Matches[4]
Port=$Matches[5]
}
}|Export-Csv NewLogFile.csv -NoTypeInfo
That would give you a CSV you could open in Excel or something with just the job number, user ID, the computer they used, the printer it went to, and the port it went on.
TheMadTechnician's Answer already covers a majority of this.
$a = Get-Content original.csv
$a[0] += ",Data"
$a | Set-Content updated.csv
$csv = Import-Csv updated.csv
$data = $csv.where({$_."Event ID" -eq 307}) | Select-String -pattern "(?<=owned by )(?<user>[^ ]+)(?: on )(?<host>.*?)(?: was printed on )(?<printer>.*?)(?= through )"
$(ForEach ($m in $data.matches) {
[pscustomobject][ordered]#{"User"=$m.groups["user"].value
"Host"=$m.groups["host"].value
"Printer"=$m.groups["printer"].value
}
}) | Export-Csv Output.csv -notypeinformation
There are some issues with the CSV that is exported from the Windows print server. If the numbered issues below do not matter in this case, then I can just remove this answer.
The CSV column that contains the data you care about has no name. The other columns have headers, but for some reason this one does not. Without that header, your Import-Csv command will be useless. The first four lines of the code cover adding the Data header to that file. So you can either use code to fix that or just open the file, add the column name, and save.
The event ID you care about is 307. There's a lot of other noise in that event log unless you pre-filtered it before saving it as a CSV, and that could impact the regex matching.
My method here is really no different than the other posted answer. I'm just matching less strings and access those matches with named indexes.
This is not an answer for how to extract information from the message text but rather how to avoid having to deal with formatted text in the first place. It appears you are trying to parse the message for Event Log events with ID 307. This code is adapted from PowerShell One-Liner to Audit Print Jobs on a Windows based Print Server.
Using the Get-WinEvent cmdlet you can query a specific log (Microsoft-Windows-PrintService/Operational) for specific events (307), and then it's just a matter of retrieving and adding a meaningful name to each property...
$eventFilter = #{
LogName = 'Microsoft-Windows-PrintService/Operational';
ID = 307;
}
Get-WinEvent -FilterHashTable $eventFilter `
| Select-Object -Property `
'TimeCreated', `
#{ Name = 'JobId'; Expression = { $_.Properties[0].Value }; }, `
#{ Name = 'JobName'; Expression = { $_.Properties[1].Value }; }, `
#{ Name = 'UserName'; Expression = { $_.Properties[2].Value }; }, `
#{ Name = 'MachineName'; Expression = { $_.Properties[3].Value }; }, `
#{ Name = 'PrinterName'; Expression = { $_.Properties[4].Value }; }, `
#{ Name = 'PortName'; Expression = { $_.Properties[5].Value }; }, `
#{ Name = 'ByteCount'; Expression = { $_.Properties[6].Value }; }, `
#{ Name = 'PageCount'; Expression = { $_.Properties[7].Value }; }
For an event with a message like this...
Document 1, Print Document owned by UserName on \\MachineName was
printed on Microsoft Print to PDF through port X:\Directory\File.ext.
Size in bytes: 12345. Pages printed: 1. No user action is required.
...the above code will output an object like this...
TimeCreated : 3/28/2019 5:36:41 PM
JobId : 1
JobName : Print Document
UserName : UserName
MachineName : \\MachineName
PrinterName : Microsoft Print to PDF
PortName : X:\Directory\File.ext
ByteCount : 12345
PageCount : 1
You could pipe the above command into Export-CSV to create your CSV file, or even just use Out-GridView to view and filter the data directly in PowerShell. Either way, no parsing necessary.

Extract content from log file from share location and send email in tabular format along with new headings to email

I am trying to automate few of the daily health check tasks using PowerShell.
I would like to achieve the following (Step by Step), though I have a partial success in few,
Extract content of text (Log) file located at shared location (I have succeeded) by defining $Path = Set-Location ... etc.,
Send email (succeeded) to mail box, by defining
Real help I need is here,
I want Headings to be appended in Email, along with original extracted text from Step 1,
For ex..
Original Text looks like this (extracted from text file at shared location):
01-01-2018 Number of Successful object - 1
I would like to add the header for this on email, like
date Description Number of Objects
01-01-2018 Successful objects 1
Assuming the content from the log file gathered in Step 1 is a string array of log entries where every string has a format similar to '01-01-2018 Number of Successful object - 1 '
In this example I call that array $logEntries
# create an array to store the results objects in
$result = #()
# loop through this array of log entries
$logEntries | ForEach-Object {
# Here every text line is represented by the special variable $_
# From your question I gather they all have this format:
# '01-01-2018 Number of Successful object - 1 '
if ($_ -match '(?<date>\d{2}-\d{2}-\d{4})\s+(?<description>[^\-\d]+)[\s\-]+(?<number>\d+)\s*$') {
# Try to get the 'Succesful' or 'Failed' (??) text part out of the description
$description = ($matches['description'] -replace 'Number of|object[s]?', '').Trim() + ' object'
if ([int]$matches['number'] -ne 1) { $description += 's' }
# add to the result array
$result += New-Object -TypeName PSObject -Property ([ordered]#{
'Date' = $matches['date']
'Description' = $description
'Number of Objects' = $matches['number']
})
}
}
# now decide on the format of this result
# 1) as plain text in tabular form.
# This looks best when used with a MonoSpaced font like Courier or Consolas.
$result | Format-Table -AutoSize | Out-String
# or 2) as HTML table. You will have to style this table in your email.
# You may include a stylesheet using the '-CssUri' parameter, but inserting
# it in a nicely drawn-up HTML template will give you more creative freedom.
$result | ConvertTo-Html -As Table -Fragment
p.s. because the PSObject has [ordered] properties, this needs PowerShell version 3.0 or better.

How to get the status each logged in user status details

$dat = query user /server:$SERVER
this query gives below data
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
>vm82958 console 1 Active 1:28 2/9/2018 9:18 AM
adminhmc 2 Disc 1:28 2/13/2018 10:25 AM
nn82543 3 Disc 2:50 2/13/2018 3:07 PM
I would like to get each independent user details like STATE, USERNAME, ID details. I tried below code but it is not giving any data
foreach($proc in $dat) {
$proc.STATE # This is not working this command not giving any data.
$proc.ID # This is not working this command not giving any data.
}
Please help me on this.
The result of $dat.GetType() is:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
This is very similar to this StackOverflow post, but you have blank fields in your data.
One solution is to deal with this first. Example below but this may break given data that is very different to your example. For a more robust and complete solution see Matt's comment
# replace 20 spaces or more with TWO commas, because it signifies a missing field
$dat2 = $dat.Trim() -replace '\s{20,}', ',,'
# replace 2 spaces or more with a single comma
$datTable = $dat2.Trim() -replace '\s{2,}', ',,'
foreach($proc in $datTable) {
$proc.STATE
$proc.ID
}
Another option is to use fixed Columns with string.Insert , like this:
$content = quser /server:$SERVER
$columns = 14,42,46,54,65 | Sort -Descending
$Delimiter = ','
$dat = $content | % {
$line = $_
$columns | % {
$line = $line.Insert($_, $Delimiter)
}
$line -replace '\s'
} |
ConvertFrom-Csv -Delimiter $Delimiter
And Then:
foreach($proc in $dat) {
$proc.STATE
$proc.ID # Will show the relevant Data
}