Parsing CCM/Logs – Part 2 using a Dynamic Parameter


In the first Blog post Parsing CCM\Logs I showed you how I was able to get a Script from the community and make a few tweaks to allow for it to parse logs for CCM.  In this blog post I’m going to show you how I took the next step.   My next step was to use the parsing logic in a script and incorporate a Dynamic Parameter.

To begin with I wanted to have the user not have to go to the machine and find every log for CCM\logs and then type in the value of that log name.  For instance in my directory for c:\windows\ccm\logs there were 178 files with the .log extension.  Trying to create a Validate set for this many logs is also problematic.  So I chose the dynamic Parameter approach for this function.  Now on to the script.

The first portion of the script is standard parameter values.


param([Parameter(Mandatory=$true,Position=0)]$ComputerName = '$env:computername', [Parameter(Mandatory=$true,Position=1)]$path = 'c:\windows\ccm\logs')

The next portion of this script is where the “magic” is.  The next parameter -log is created dynamically from the first two variables ($computerName, $path).


DynamicParam
{
$ParameterName = 'Log'
if($path.ToCharArray() -contains ':')
{

$FilePath = "\\$ComputerName\$($path -replace ':','$')"
}
else
{
$FilePath = "\\$computerName\$((get-item $path).FullName -replace ':','$')"
}

$logs = Get-ChildItem "$FilePath\*.log"
$LogNames = $logs.basename

$logAttribute = New-Object System.Management.Automation.ParameterAttribute
$logAttribute.Position = 2
$logAttribute.Mandatory = $true
$logAttribute.HelpMessage = 'Pick A log to parse'

$logCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$logCollection.add($logAttribute)

$logValidateSet = New-Object System.Management.Automation.ValidateSetAttribute($LogNames)
$logCollection.add($logValidateSet)

$logParam = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName,[string],$logCollection)

$logDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$logDictionary.Add($ParameterName,$logParam)
return $logDictionary

}

To explain what is going on here I’ll start with the code I used to get me to this point. Martin Schvartzman wrote a great article that showed how to do most of what I’ve posted here Dynamic ValidateSet in a Dynamic Parameter. 

I’ll do my best to explain how his code works.  First step is the DynamicParam Statement.  This tells PowerShell that we are going to create a dynamic Parameter.  Very simply stated a dynamic parameter is a Parameter that is added at runtime only when needed.

The first thing that is done in this dynamic parameter is to create a Runtime Defined Parameter Dictionary.  In order to add a runtime parameter we have to define the parameter and it’s attributes and then add it to the collection to return to the runtime so it will be added properly.

<pre>System.Management.Automation.RuntimeDefinedParameterDictionary</pre>

This next portion of the code creates a object that will contain our parameter attributes.


$logAttribute = New-Object System.Management.Automation.ParameterAttribute

For the purposes of this script we are only going to make the parameter mandatory, set it’s position in the pipeline, and create a help message.  There are other items that can be defined if required.  We can see this by getting the members of the $logAttribute:


$logAttribute  | get-member -properties

TypeName: System.Management.Automation.ParameterAttribute

Name MemberType Definition
---- ---------- ----------
DontShow Property bool DontShow {get;set;}
HelpMessage Property string HelpMessage {get;set;}
HelpMessageBaseName Property string HelpMessageBaseName {get;set;}
HelpMessageResourceId Property string HelpMessageResourceId {get;set;}
Mandatory Property bool Mandatory {get;set;}
ParameterSetName Property string ParameterSetName {get;set;}
Position Property int Position {get;set;}
TypeId Property System.Object TypeId {get;}
ValueFromPipeline Property bool ValueFromPipeline {get;set;}
ValueFromPipelineByPropertyName Property bool ValueFromPipelineByPropertyName {get;set;}
ValueFromRemainingArguments Property bool ValueFromRemainingArguments {get;set;}

Since we are going to need this set of attributes in our parameter we need to add this to the Attribute collection ($logCollection) that will in turn be added to the runtime Parameter $logParam.

Next we’ll create our Validate set item from the list of logs on the remote machine which was gathered with the FilePath variable then added to a new object that will contain our ValidateSet attributes. Then add it to our LogCollection.


$FilePath = "\\$ComputerName\$($path -replace ':','$')"

<br>

$logValidateSet = New-Object System.Management.Automation.ValidateSetAttribute($LogNames)

 $logCollection.add($logValidateSet)

Finally we’ll add our parameter name and LogCollection to a Runtime Defined parameter.  Then put this all in our Runtime Defined Parameter Dictionary. Then hand it back to PowerShell.


$logParam = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName,[string],$logCollection)

$logDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
 $logDictionary.Add($ParameterName,$logParam)
 return $logDictionary

Now  that we have the Full explanation of the Dynamic Parameter we can stitch our previous Log Parser together with this function to give us back any one of the logs on our remote machine.  We’ll put this in our Process block of our function:


 $sb2 = "$((Get-ChildItem function:get-cmlog).scriptblock)`r`n"
 $sb1 = [scriptblock]::Create($sb2)
 $results = Invoke-Command -ComputerName $ComputerName -ScriptBlock $sb1 -ArgumentList "$path\$log.log"
 [PSCustomObject]@{"$($log)Log"=$results}

Now when we call Get-CcmLog we’ll get a return with a parsed log that has Log appended in the object name.

dynparam3

Full code is posted in a gist here:


function Get-CMLog
{
<#
.SYNOPSIS
Parses logs for System Center Configuration Manager.
.DESCRIPTION
Accepts a single log file or array of log files and parses them into objects. Shows both UTC and local time for troubleshooting across time zones.
.PARAMETER Path
Specifies the path to a log file or files.
.INPUTS
Path/FullName.
.OUTPUTS
PSCustomObject.
.EXAMPLE
C:\PS> Get-CMLog -Path Sample.log
Converts each log line in Sample.log into objects
UTCTime : 7/15/2013 3:28:08 PM
LocalTime : 7/15/2013 2:28:08 PM
FileName : sample.log
Component : TSPxe
Context :
Type : 3
TID : 1040
Reference : libsmsmessaging.cpp:9281
Message : content location request failed
.EXAMPLE
C:\PS> Get-ChildItem -Path C:\Windows\CCM\Logs | Select-String -Pattern 'failed' | Select -Unique Path | Get-CMLog
Find all log files in folder, create a unique list of files containing the phrase 'failed, and convert the logs into objects
UTCTime : 7/15/2013 3:28:08 PM
LocalTime : 7/15/2013 2:28:08 PM
FileName : sample.log
Component : TSPxe
Context :
Type : 3
TID : 1040
Reference : libsmsmessaging.cpp:9281
Message : content location request failed
.LINK
http://blog.richprescott.com
#>
param(
[Parameter(Mandatory=$true,
Position=0,
ValueFromPipelineByPropertyName=$true)]
[Alias("FullName")]
$Path,
$tail =10
)
PROCESS
{
if(($Path -isnot [array]) -and (test-path $Path PathType Container) )
{
$Path = Get-ChildItem "$path\*.log"
}
foreach ($File in $Path)
{
if(!( test-path $file))
{
$Path +=(Get-ChildItem "$file*.log").fullname
}
$FileName = Split-Path Path $File Leaf
if($tail)
{
$lines = Get-Content Path $File tail $tail
}
else {
$lines = get-Content path $file
}
ForEach($l in $lines ){
$l -match '\<\!\[LOG\[(?<Message>.*)?\]LOG\]\!\>\<time=\"(?<Time>.+)(?<TZAdjust>[+|-])(?<TZOffset>\d{2,3})\"\s+date=\"(?<Date>.+)?\"\s+component=\"(?<Component>.+)?\"\s+context="(?<Context>.*)?\"\s+type=\"(?<Type>\d)?\"\s+thread=\"(?<TID>\d+)?\"\s+file=\"(?<Reference>.+)?\"\>' | Out-Null
if($matches)
{
$UTCTime = [datetime]::ParseExact($("$($matches.date) $($matches.time)$($matches.TZAdjust)$($matches.TZOffset/60)"),"MM-dd-yyyy HH:mm:ss.fffz", $null, "AdjustToUniversal")
$LocalTime = [datetime]::ParseExact($("$($matches.date) $($matches.time)"),"MM-dd-yyyy HH:mm:ss.fff", $null)
}
[pscustomobject]@{
UTCTime = $UTCTime
LocalTime = $LocalTime
FileName = $FileName
Component = $matches.component
Context = $matches.context
Type = $matches.type
TID = $matches.TI
Reference = $matches.reference
Message = $matches.message
}
}
}
}
}
function Get-CCMLog
{
param([Parameter(Mandatory=$true,Position=0)]$ComputerName = '$env:computername', [Parameter(Mandatory=$true,Position=1)]$path = 'c:\windows\ccm\logs')
DynamicParam
{
$ParameterName = 'Log'
if($path.ToCharArray() -contains ':')
{
$FilePath = "\\$($ComputerName)\$($path -replace ':','$')"
}
else
{
$FilePath = "\\$($ComputerName)\$((get-item $path).FullName -replace ':','$')"
}
$logs = Get-ChildItem "$FilePath\*.log"
$LogNames = $logs.basename
$logAttribute = New-Object System.Management.Automation.ParameterAttribute
$logAttribute.Position = 2
$logAttribute.Mandatory = $true
$logAttribute.HelpMessage = 'Pick A log to parse'
$logCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$logCollection.add($logAttribute)
$logValidateSet = New-Object System.Management.Automation.ValidateSetAttribute($LogNames)
$logCollection.add($logValidateSet)
$logParam = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName,[string],$logCollection)
$logDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$logDictionary.Add($ParameterName,$logParam)
return $logDictionary
}
begin {
# Bind the parameter to a friendly variable
$Log = $PsBoundParameters[$ParameterName]
}
process {
$sb2 = "$((Get-ChildItem function:get-cmlog).scriptblock)`r`n"
$sb1 = [scriptblock]::Create($sb2)
$results = Invoke-Command ComputerName $ComputerName ScriptBlock $sb1 ArgumentList "$path\$log.log"
[PSCustomObject]@{"$($log)Log"=$results}
}
}

view raw

get-ccmlog.ps1

hosted with ❤ by GitHub

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s