Welcome back!
Last post I talked about using the Log Parser executable from PowerShell. I also briefly mentioned the Log Parser COM component. In this post I will go into more depth on using the COM component from PowerShell.
The COM component exposes a simple object model consisting of only three main objects.
- LogQuery – Object used to execute queries and batches
- LogRecordSet – Object returned by LogQuery.Execute method
- LogRecord – Child object of LogRecordset. Object returned by LogRecordSet.getRecord method
There are also objects containing the input and output formats. There are several of them and they are well documented in the Log Parser documentation. I will give examples as we progress.
The first step on my task to integrate with the Log Parser COM object was to put together a function library of the basic building blocks.
The library consists of the following functions.
- Get-LPInputFormat – Returns a Log Parser input format object
- Get-LPOutputFormat – Returns a Log Parser output format object
- Invoke-LPExecute – Executes the LogQuery.Execute method and returns a LogRecordSet
- Invoke-LPExecuteBatch – Executes the LogQuery.ExecuteBatch method to output the query in the requested format
- Get-LPRecordSet – Executes a Log Parser Query and returns a LogRecordSet as an array of PowerShell objects
- Get-LPRecord – Returns LogRecord object as a PowerShell object from the current record of a LogRecordSet object
With these functions we can support almost all of the Log Parser functionality in PowerShell. I did not build in support for the custom COM input type or the NAT and Datagrid output types. The NAT and Datagrid output types can be handled in a different way in PowerShell. The COM input format is a challenge I left for another day.
Here is the function library.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# --------------------------------------------------------------------------- ### <library name="Muegge_LogParser_Lib.ps1"> ### <author>David Muegge</author> ### <createdate>20081108</createdate> ### <modifieddate>20081121</modifieddate> ### <description> ### Log Parser function library ### </description> ### <dependencies> ### Log Parser 2.2 COM component ### </dependencies> ### <usage> ### Dot source from calling script ### </usage> ### </library> # --------------------------------------------------------------------------- # --------------------------------------------------------------------------- ### <function name="Get-LPInputFormat"> ### <description> ### Returns Log Parser Input Format object based on passed string ### </description> ### <usage> ### Get-LPInputFormat -InputType <string> ### </string></usage> ### </function> # --------------------------------------------------------------------------- function Get-LPInputFormat{ param([String]$InputType) switch($InputType.ToLower()){ "ads"{$inputobj = New-Object -comObject MSUtil.LogQuery.ADSInputFormat} "bin"{$inputobj = New-Object -comObject MSUtil.LogQuery.IISBINInputFormat} "csv"{$inputobj = New-Object -comObject MSUtil.LogQuery.CSVInputFormat} "etw"{$inputobj = New-Object -comObject MSUtil.LogQuery.ETWInputFormat} "evt"{$inputobj = New-Object -comObject MSUtil.LogQuery.EventLogInputFormat} "fs"{$inputobj = New-Object -comObject MSUtil.LogQuery.FileSystemInputFormat} "httperr"{$inputobj = New-Object -comObject MSUtil.LogQuery.HttpErrorInputFormat} "iis"{$inputobj = New-Object -comObject MSUtil.LogQuery.IISIISInputFormat} "iisodbc"{$inputobj = New-Object -comObject MSUtil.LogQuery.IISODBCInputFormat} "ncsa"{$inputobj = New-Object -comObject MSUtil.LogQuery.IISNCSAInputFormat} "netmon"{$inputobj = New-Object -comObject MSUtil.LogQuery.NetMonInputFormat} "reg"{$inputobj = New-Object -comObject MSUtil.LogQuery.RegistryInputFormat} "textline"{$inputobj = New-Object -comObject MSUtil.LogQuery.TextLineInputFormat} "textword"{$inputobj = New-Object -comObject MSUtil.LogQuery.TextWordInputFormat} "tsv"{$inputobj = New-Object -comObject MSUtil.LogQuery.TSVInputFormat} "urlscan"{$inputobj = New-Object -comObject MSUtil.LogQuery.URLScanLogInputFormat} "w3c"{$inputobj = New-Object -comObject MSUtil.LogQuery.W3CInputFormat} "xml"{$inputobj = New-Object -comObject MSUtil.LogQuery.XMLInputFormat} } return $inputobj } # --------------------------------------------------------------------------- ### <function name="Get-LPOutputFormat"> ### <description> ### Returns Log Parser Output Format object based on passed string ### </description> ### <usage> ### Get-LPOutputFormat -OutputType <string> ### </string></usage> ### </function> # --------------------------------------------------------------------------- function Get-LPOutputFormat{ param([String]$OutputType) switch($OutputType.ToLower()){ "csv"{$outputobj = New-Object -comObject MSUtil.LogQuery.CSVOutputFormat} "chart"{$outputobj = New-Object -comObject MSUtil.LogQuery.ChartOutputFormat} "iis"{$outputobj = New-Object -comObject MSUtil.LogQuery.IISOutputFormat} "sql"{$outputobj = New-Object -comObject MSUtil.LogQuery.SQLOutputFormat} "syslog"{$outputobj = New-Object -comObject MSUtil.LogQuery.SYSLOGOutputFormat} "tsv"{$outputobj = New-Object -comObject MSUtil.LogQuery.TSVOutputFormat} "w3c"{$outputobj = New-Object -comObject MSUtil.LogQuery.W3COutputFormat} "tpl"{$outputobj = New-Object -comObject MSUtil.LogQuery.TemplateOutputFormat} } return $outputobj } # --------------------------------------------------------------------------- ### <function name="Invoke-LPExecute"> ### <description> ### Executes a Log Parser Query and returns a recordset ### </description> ### <usage> ### Invoke-LPExecute -query <string> ### </string></usage> ### </function> # --------------------------------------------------------------------------- function Invoke-LPExecute{ param([string] $query, $inputtype) $LPQuery = new-object -com MSUtil.LogQuery if($inputtype){ $LPRecordSet = $LPQuery.Execute($query, $inputtype) } else { $LPRecordSet = $LPQuery.Execute($query) } return $LPRecordSet } # --------------------------------------------------------------------------- ### <function name="Invoke-LPExecuteBatch"> ### <description> ### Executes Log Parser batch query with passed input and output types ### </description> ### <usage> ### Invoke-LPExecuteBatch -query <string> -inputtype <logparserinputformat> -outputtype <logparseroutputformat> ### </logparseroutputformat></logparserinputformat></string></usage> ### </function> # --------------------------------------------------------------------------- function Invoke-LPExecuteBatch{ param([string]$query, $inputtype, $outputtype) $LPQuery = new-object -com MSUtil.LogQuery $result = $LPQuery.ExecuteBatch($query, $inputtype, $outputtype) return $result } # --------------------------------------------------------------------------- ### <function name="Get-LPRecord"> ### <description> ### Returns PowerShell custom object from Log Parser recordset for current record ### </description> ### <usage> ### Get-LPRecord -rs <recordset> ### </recordset></usage> ### </function> # --------------------------------------------------------------------------- function Get-LPRecord{ param($LPRecordSet) $LPRecord = new-Object System.Management.Automation.PSObject if( -not $LPRecordSet.atEnd()) { $Record = $LPRecordSet.getRecord() for($i = 0; $i -lt $LPRecordSet.getColumnCount();$i++) { $LPRecord | add-member NoteProperty $LPRecordSet.getColumnName($i) -value $Record.getValue($i) } } return $LPRecord } # --------------------------------------------------------------------------- ### <function name="Get-LPRecordSet"> ### <description> ### Executes a Log Parser Query and returns a LogRecordSet as a custom powershell object ### </description> ### <usage> ### Get-LPRecordSet -query <string> ### </string></usage> ### </function> # --------------------------------------------------------------------------- function Get-LPRecordSet{ param([string]$query) # Execute Query $LPRecordSet = Invoke-LPExecute $query $LPRecords = new-object System.Management.Automation.PSObject[] 0 for(; -not $LPRecordSet.atEnd(); $LPRecordSet.moveNext()) { # Add record $LPRecord = Get-LPRecord($LPRecordSet) $LPRecords += new-Object System.Management.Automation.PSObject $RecordCount = $LPQueryResult.length-1 $LPRecords[$RecordCount] = $LPRecord } $LPRecordSet.Close(); return $LPRecords } |
This library provides the basic functionality needed for Log Parser. We can use it in two basic scenarios.
One – We want to execute a Log Parser batch. This mode works exactly as the Log Parser command line tool works it queries an input file of a given type and writes the results to an output file of a given type.
Two – We want the results of a Log Parser query returned to a PowerShell object. This will allow us to further process the results using PowerShell or simply utilize the output facilities to display the results.
Example of Scenario One:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
. .Muegge_LogParser_Lib.ps1 $infile = "D:TempLPTest.log" $outfile = "D:TempLPTest.gif" # Set input format and parameters $inputformat = Get-LPInputFormat "w3c" # Set output format and parameters $outputformat = Get-LPOutputFormat "chart" $outputformat.chartTitle = "Top 10 Pages" $outputformat.chartType = "BarStacked" $outputformat.groupSize = "600x400" $query = @" SELECT Top 10 cs-uri-stem, Count(*) INTO $outfile FROM $infile Group By cs-uri-stem Order by cs-uri-stem desc "@ # Execute batch Invoke-LPExecuteBatch $query $inputformat $outputformat | Out-Null |
The code above provides a good way to create scheduled reports. The syntax is easier to follow than the command line switches at least for me.
Here is the sample output chart.
Yes, I took this log from a box with nothing but hacker traffic J
Now let’s look at the second scenario. We will return the results of a Log Parser query in a PSObject.
Here we are querying for application hang events in the application event log. We will use a Log Parser query to retrieve just the events we want. Then we will use PowerShell to filter out just the events for IE. The we can easily display the output any way we like.
1 2 3 4 5 6 7 8 9 10 11 |
. .Muegge_LogParser_Lib.ps1 $query = @" SELECT * FROM Application WHERE EventID = 1002 "@ $inputformat = Get-LPInputFormat "evt" $records = Get-LPRecordSet $query $inputformat $records | where {$_.Strings -like "*iexplore.exe*"} | ft -property TimeGenerated,SourceName,Strings |
Here is the output.
1 2 3 4 5 6 7 8 9 10 11 |
TimeGenerated SourceName Strings ------------- ---------- ------- 11/1/2008 11:21:02 PM Application Hang iexplore.exe|7.0.6000.16735|hungapp|0.0... 11/2/2008 6:25:35 PM Application Hang iexplore.exe|7.0.6000.16735|hungapp|0.0... 11/4/2008 7:32:35 AM Application Hang iexplore.exe|7.0.6000.16735|hungapp|0.0... 11/6/2008 4:21:22 PM Application Hang iexplore.exe|7.0.6000.16735|hungapp|0.0... 11/8/2008 7:05:20 PM Application Hang iexplore.exe|7.0.6000.16735|hungapp|0.0... 11/10/2008 3:01:45 PM Application Hang iexplore.exe|7.0.6000.16735|hungapp|0.0... 11/11/2008 5:10:40 PM Application Hang iexplore.exe|7.0.6000.16735|hungapp|0.0... 11/14/2008 12:13:06 PM Application Hang iexplore.exe|7.0.6000.16735|hungapp|0.0... 11/17/2008 7:34:29 AM Application Hang iexplore.exe|7.0.6000.16735|hungapp|0.0... |
To me this really seems like the best of both worlds utilizing each tool for its strength.
Now I just have to figure out why IE hangs J
Best Regards,
Dave
16 comments
Hi Dave,
I’m really impressed with your Library. Basicly it supports extact the Features that i need. Thanks it safed me alot of Time.
For a personal Project i added the following Method to the Library.
# —————————————————————————
###
###
### Creates a Table Object from a LogRecordSet
###
###
### Get-LPTableResults -query -inputtype
###
###
# —————————————————————————
function Get-LPTableResults
{
param([string] $query, $inputtype)
$rsLP = Invoke-LPExecute $query $inputtype
$tab = new-object System.Data.DataTable(“Results”)
$i = 0
for($i = 0; $i -lt $rsLP.getColumnCount();$i++)
{
$col = new-object System.Data.DataColumn;
$ct = $rsLP.getColumnType($i)
$col.ColumnName = $rsLP.getColumnName($i);
switch ($rsLP.getColumnType($i))
{
“1”
{
$col.DataType = [System.Type]::GetType(“System.Int32”);
break;
}
“2”
{
$col.DataType = [System.Type]::GetType(“System.Double”);
break;
}
“4”
{
$col.DataType = [System.Type]::GetType(“System.DateTime”);
break;
}
default
{
$col.DataType = [System.Type]::GetType(“System.String”);
break;
}
}
$tab.Columns.Add($col);
}
$i = 0
while(-not $rsLP.atEnd())
#for(; -not $rsLP.atEnd(); $rsLP.moveNext())
{
$rowLP = $rsLP.getRecord();
$row = $tab.NewRow();
for ($i = 0; ($i -lt $rsLP.getColumnCount()); ($i++))
{
$columnName = $rsLP.getColumnName($i)
$row.$columnName = $rowLP.getValue($i);
}
$tab.Rows.Add($row);
$rsLP.moveNext();
}
$ds = new-object System.Data.DataSet
$ds.Tables.Add($tab)
return $ds.Tables[“Results”]
}
Maybe it for others usefull to.
Best Regards Ivo
Ivo,
I am glad you found it useful!
Thanks for the addition.
Dave
Great post! I referenced it on my blog at http://fatbeards.blogspot.com/2009/02/powershelllog-parser-22-and-add-member.html
Thx much for the contribution…would be very interesting to see the additions and refinements since the original post. What sorts of lessons have you learned since?
This looks great. one question. I would love to use PowerShell and LogParser to grab the Event Logs from my domain controllers and send them to a SQL server for further analysis. How would you recommend doing something like that? I am just getting started with PowerShell and LogParser so any assistance would be greatly appreciated.
Thanks
app
Aaron,
Thanks for the feedback.
Log Parser has the ability to output data to SQL inherently. This is done via the SQL output format. The Log Parser help file has information and examples under the output formats section.
To use my library to do this in PowerShell you would do something like this:
. .Muegge_LogParser_Lib.ps1
$query = @”
SELECT *
FROM Application
WHERE EventID = 1002
“@
$inputformat = Get-LPInputFormat “evt”
$outputformat = Get-LPOutputFormat “sql”
$outputformat.server = “SVRNAME”
$outputformat.database = “DBNAME”
$outputformat.username = “USRNAME”
$outputformat.password = “PASSWORD”
Invoke-LPExecuteBatch $query $inputformat $outputformat
You could also just return objects and use a PowerShell library such as SQLPSX ( http://sqlpsx.codeplex.com ) to add the data to SQL. This would give a greater degree of control but would also be more complex.
Regards,
Dave
Hi Dave,
I’m new to PowerShell and your library was exactly what I needed. This library is awesome, I was able to implement your first scenario on my own data [input:TSV file, output:TSV file] (I’m doing this against SharePoint UML log files in TSV format). Commands below:
$infile = “C:abcMacine1-*.log”
$outfile = “C:abcMy_Error_Results.tsv”
$inputformat = Get-LPInputFormat “tsv”
$outputformat = Get-LPOutputFormat “tsv”
SELECT Timestamp, Area, Category, Level, Message
INTO $outfile
FROM $infile
Where Message LIKE ‘%abc%’
ORDER BY Timestamp
Invoke-LPExecuteBatch $query $inputformat $outputformat | Out-Null
Although not very useful, ultra cool :).
However, when I tried to implement your second scenario on my own data [input:TSV file, output:PSObject], I always get exceptions stating that the columns that I asked for in the query do not exist. Commands below:
$infile = “C:abcMachine1-*.log”
$inputformat = Get-LPInputFormat “tsv”
$query2 = @”
SELECT Timestamp, Area, Category, Level, Message
FROM $infile
Where Message LIKE ‘%abc%’
ORDER BY Timestamp
“@
$records = Get-LPRecordSet $query2 $inputformat —Exception Occurs Here
$records | where{$_.Message -like “*bsc*”}
I’m sure i’m missing something increadably simple, please help…:)
How do you include the -checkpoint value into the Invoke-LPExcute command in the Powershell script if you want to use your logparser library to monitor a log file so it only queries the new log file lines since the last iterration?
I tried:
# Execute batch
Invoke-LPExecuteBatch $query $inputformat -checkpoint:
but this is not working.
Here is my PS code snippet:
$infile =
$checkpoint =
$query = @”
Select Text FROM $infile WHERE Text LIKE ‘%ERROR:%’
“@
$inputformat = Get-LPInputFormat “TEXTLINE”
Invoke-LPExecute $query $inputformat -checkpoint:$checkpoint
Script runs fine but no check file is created
This saved me a lot of time on a recent project that required some log-digging across multiple servers. Now I have something reusable and clean enough that other folks will be able to make sense of it down the line. Thanks a lot, great stuff.
For those interested, I was able to get the Checkpoint file to work. Here is the code I added to the Invoke-LPExecuteBatch function.
function Invoke-LPExecuteBatch{
param([string]$query, $inputtype, $outputtype, $checkpoint)
$LPQuery = new-object -com MSUtil.LogQuery
$inputtype.iCheckpoint = $checkpoint
$result = $LPQuery.ExecuteBatch($query, $inputtype, $outputtype)
return $result
}
This is a really excellent and re-usage code library that works straight off. Thank you very much. Saved us a lot of time.
This post has really helped me nail a tough and late requirement, so thanks David. I did find a couple of bugs in the code that I thought I’d share, as follows:
1) The Get-LPRecordSet function requires a 2nd paramter, $inputtype, that needs passing to the Invoke-LPExecute function like this:
$LPRecordSet = Invoke-LPExecute $query $inputtype
2) In the Get-LPRecordSet function there’s a line that reads:
$RecordCount = $LPQueryResult.length-1
Since the $LPQueryResult variable doesn’t exist and should read:
$RecordCount = $LPRecordSet.length-1
Actually, I think it should just be like this – using a “-1” to hit the last item in the array. The fact that it previously evaluated to “-1” anyhow (due to the bug) was just a bit of luck.
for (; -not $logParserRecordSet.atEnd(); $logParserRecordSet.moveNext()) {
# Add record
$logParserRecord = Get-LogParserRecord($logParserRecordSet)
$logParserRecords += New-Object System.Management.Automation.PSObject
$logParserRecords[-1] = $LogParserRecord
}
I’ve tried following Pete’s advice above for enabling checkpoint functionality.
It looks like $inputobj does not even have an “iCheckpoint” property.
formatMessage
fullEventCode
fullText
ignoreMessageErrors
resolveSIDs
I tried setting “iCheckpoint” as a note property without success.
Hi,
Want to thank you for this great post. With this COM components exposed, is there anyway I can specify the day parameter? For example, logparser.exe file:test.sql?source=””+day=”2″ -o:NAT will look back 2 days of log files. I am just not sure how to specify that in this case.
Yean,
Thanks for the comment, but honestly I did this post in 2008 and have not worked with this in years. The only recommendation I can give you is to look at the .chm help file that is installed with logparser. If I remember correctly you would use the datetime functions provided by logparser in your sql query to accomplish your goal. Sorry I could not provide a more specific answer, but I would need to go back and learn it again myself.
Regards,
Dave