W3CLogs.psm1
# Copyright WebMD Health Services # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License #Requires -Version 5.1 Set-StrictMode -Version 'Latest' # Functions should use $moduleRoot as the relative root from which to find # things. A published module has its function appended to this file, while a # module in development has its functions in the Functions directory. $script:moduleRoot = $PSScriptRoot $script:fieldPropertyMap = @{ 'date' = 'Date'; 'time' = 'Time'; 's-ip' = 'ServerIP'; 'cs-method' = 'Method'; 'cs-uri-stem' = 'Stem'; 'cs-uri-query' = 'Query'; 's-port' = 'Port'; 'cs-username' = 'UserName'; 'c-ip' = 'ClientIP'; 'cs-version' = 'Version'; 'cs(User-Agent)' = 'UserAgent'; 'cs(Cookie)' = 'Cookie'; 'cs(Referer)' = 'Referer'; 'cs-host' = 'Host'; 'sc-status' = 'Status'; 'sc-bytes' = 'BytesSent'; 'cs-bytes' = 'BytesReceived'; 'time-taken' = 'TimeTaken'; 's-sitename' = 'SiteName'; 's-computername' = 'ComputerName'; 'sc-substatus' = 'Substatus'; 'sc-win32-status' = 'Win32Status'; } $script:milliseconds = [Collections.Generic.Hashset[String]]::New() [void]$script:milliseconds.Add('time-taken') $script:httpMethods = [Collections.Generic.Hashset[String]]::New() [void]$script:httpMethods.Add('sc-method') $srcPath = Join-Path -Path $script:moduleRoot -ChildPath 'src' -Resolve Add-Type -Path (Get-ChildItem -Path $srcPath -Filter '*.cs').FullName ` -ReferencedAssemblies 'System.Net.Http','System.Net','System.Net.Primitives' # Store each of your module's functions in its own file in the Functions # directory. On the build server, your module's functions will be appended to # this file, so only dot-source files that exist on the file system. This allows # developers to work on a module without having to build it first. Grab all the # functions that are in their own files. $functionsPath = Join-Path -Path $moduleRoot -ChildPath 'Functions\*.ps1' if( (Test-Path -Path $functionsPath) ) { foreach( $functionPath in (Get-Item $functionsPath) ) { . $functionPath.FullName } } function Import-W3CLog { <# .SYNOPSIS Parses and imports W3C log files. .DESCRIPTION The `Import-W3CLog` function parses and imports W3C log files, returning objects representing each line. Pass the path to the log file to parse to the `Path` parameter, or to parse multiple files, pipe them into the function. .EXAMPLE Import-W3CLog -Path log.log Demonstrates how to parse and import a single W3C log file by passing its path to the `Path` parameter. .EXAMPLE Get-ChildItem -Path C:\Inetpub\logs -Filter '*.log' -Recurse | Import-W3CLog Demonstrates how to parse multiple logs by piping their paths to the `Import-W3CLog` function. #> [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('FullName')] [String] $Path ) process { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $Path = $Path | Resolve-Path if (-not $Path) { return } $displayPath = $Path | Resolve-Path -Relative if ($displayPath.StartsWith('..')) { $displayPath = $Path } Write-Verbose $displayPath $fields = @() $lineNum = 0 foreach ($line in (Get-Content -Path $Path)) { $lineNum += 1 if (-not $line) { continue } if ($line.StartsWith('#')) { if ($line.StartsWith('#Fields: ')) { $fields = $line.Split(' ') | Select-Object -Skip 1 } else { Write-Verbose " $($line.Substring(1))" } continue } $errMsgPrefix = "$($displayPath) line $($lineNum): " $entry = [W3CLogs.LogEntry]::New() [String[]]$values = $line.Split(' ') for ($idx = 0; $idx -lt $values.Length; ++$idx) { $propertyName = $fieldName = $fields[$idx] if ($script:fieldPropertyMap.ContainsKey($fieldName)) { $propertyName = $script:fieldPropertyMap[$fieldName] } else { $entry | Add-Member -Name $fieldName -MemberType NoteProperty } $value = $values[$idx] if ($value -eq '-') { continue } if ($script:httpMethods.Contains($fieldName)) { $value = [Net.Http.HttpMethod]::New($value) } elseif ($script:milliseconds.Contains($fieldName)) { $value = [TimeSpan]::New(0, 0, 0, 0, $value) } try { $entry.$propertyName = $value } catch { $msg = "$($errMsgPrefix)Failed to convert $($fieldName) value ""$($value)"": $($_)" Write-Error $msg -ErrorAction $ErrorActionPreference } } $entry.DateTime = $entry.Date + $entry.Time $hostname = 'example.com' if ($entry.Host) { $hostname = $entry.Host } elseif ($entry.ServerIP -and $entry.ServerIP.AddressFamily -eq [Net.Sockets.AddressFamily]::InterNetwork) { $hostname = $entry.ServerIP.IPAddressToString } $queryString = '' if ($entry.Query) { $queryString = "?$($entry.Query)" } $stem = $entry.Stem if (-not $stem.StartsWith('/')) { $stem = "/$($stem)" } $url = "http://$($hostname)$($stem)$($queryString)" try { $entry.Url = [Uri]::New($url) } catch { $msg = "$($errMsgPrefix)Failed to convert ""$($url)"" to a [Uri] object: $($_)" Write-Error $msg -ErrorAction $ErrorActionPreference } $entry | Write-Output } } } function Use-CallerPreference { <# .SYNOPSIS Sets the PowerShell preference variables in a module's function based on the callers preferences. .DESCRIPTION Script module functions do not automatically inherit their caller's variables, including preferences set by common parameters. This means if you call a script with switches like `-Verbose` or `-WhatIf`, those that parameter don't get passed into any function that belongs to a module. When used in a module function, `Use-CallerPreference` will grab the value of these common parameters used by the function's caller: * ErrorAction * Debug * Confirm * InformationAction * Verbose * WarningAction * WhatIf This function should be used in a module's function to grab the caller's preference variables so the caller doesn't have to explicitly pass common parameters to the module function. This function is adapted from the [`Get-CallerPreference` function written by David Wyatt](https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d). There is currently a [bug in PowerShell](https://connect.microsoft.com/PowerShell/Feedback/Details/763621) that causes an error when `ErrorAction` is implicitly set to `Ignore`. If you use this function, you'll need to add explicit `-ErrorAction $ErrorActionPreference` to every `Write-Error` call. Please vote up this issue so it can get fixed. .LINK about_Preference_Variables .LINK about_CommonParameters .LINK https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d .LINK http://powershell.org/wp/2014/01/13/getting-your-script-module-functions-to-inherit-preference-variables-from-the-caller/ .EXAMPLE Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Demonstrates how to set the caller's common parameter preference variables in a module function. #> [CmdletBinding()] param ( [Parameter(Mandatory)] #[Management.Automation.PSScriptCmdlet] # The module function's `$PSCmdlet` object. Requires the function be decorated with the `[CmdletBinding()]` # attribute. $Cmdlet, [Parameter(Mandatory)] # The module function's `$ExecutionContext.SessionState` object. Requires the function be decorated with the # `[CmdletBinding()]` attribute. # # Used to set variables in its callers' scope, even if that caller is in a different script module. [Management.Automation.SessionState]$SessionState ) Set-StrictMode -Version 'Latest' # List of preference variables taken from the about_Preference_Variables and their common parameter name (taken # from about_CommonParameters). $commonPreferences = @{ 'ErrorActionPreference' = 'ErrorAction'; 'DebugPreference' = 'Debug'; 'ConfirmPreference' = 'Confirm'; 'InformationPreference' = 'InformationAction'; 'VerbosePreference' = 'Verbose'; 'WarningPreference' = 'WarningAction'; 'WhatIfPreference' = 'WhatIf'; } foreach( $prefName in $commonPreferences.Keys ) { $parameterName = $commonPreferences[$prefName] # Don't do anything if the parameter was passed in. if( $Cmdlet.MyInvocation.BoundParameters.ContainsKey($parameterName) ) { continue } $variable = $Cmdlet.SessionState.PSVariable.Get($prefName) # Don't do anything if caller didn't use a common parameter. if( -not $variable ) { continue } if( $SessionState -eq $ExecutionContext.SessionState ) { Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false } else { $SessionState.PSVariable.Set($variable.Name, $variable.Value) } } } |