WinEventLogCustomization.psm1
$script:ModuleRoot = $PSScriptRoot $script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\WinEventLogCustomization.psd1").ModuleVersion # Detect whether at some level dotsourcing was enforced $script:doDotSource = Get-PSFConfigValue -FullName WinEventLogCustomization.Import.DoDotSource -Fallback $false if ($WinEventLogCustomization_dotsourcemodule) { $script:doDotSource = $true } <# Note on Resolve-Path: All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator. This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS. Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist. This is important when testing for paths. #> # Detect whether at some level loading individual module files, rather than the compiled module was enforced $importIndividualFiles = Get-PSFConfigValue -FullName WinEventLogCustomization.Import.IndividualFiles -Fallback $false if ($WinEventLogCustomization_importIndividualFiles) { $importIndividualFiles = $true } if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true } if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true } function Import-ModuleFile { <# .SYNOPSIS Loads files into the module on module import. .DESCRIPTION This helper function is used during module initialization. It should always be dotsourced itself, in order to proper function. This provides a central location to react to files being imported, if later desired .PARAMETER Path The path to the file to load .EXAMPLE PS C:\> . Import-ModuleFile -File $function.FullName Imports the file stored in $function according to import policy #> [CmdletBinding()] Param ( [string] $Path ) $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath if ($doDotSource) { . $resolvedPath } else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null) } } #region Load individual files if ($importIndividualFiles) { # Execute Preimport actions foreach ($path in (& "$ModuleRoot\internal\scripts\preimport.ps1")) { . Import-ModuleFile -Path $path } # Import all internal functions foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) { . Import-ModuleFile -Path $function.FullName } # Import all public functions $functions = (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore) $function = $functions[6] foreach ($function in $functions) { . Import-ModuleFile -Path $function.FullName } # Execute Postimport actions foreach ($path in (& "$ModuleRoot\internal\scripts\postimport.ps1")) { . Import-ModuleFile -Path $path } # End it here, do not load compiled code below return } #endregion Load individual files #region Load compiled code <# This file loads the strings documents from the respective language folders. This allows localizing messages and errors. Load psd1 language files for each language you wish to support. Partial translations are acceptable - when missing a current language message, it will fallback to English or another available language. #> Import-PSFLocalizedString -Path "$($script:ModuleRoot)\en-us\*.psd1" -Module 'WinEventLogCustomization' -Language 'en-US' function Import-Excel { <# .Synopsis Import-Excel .DESCRIPTION Imports data from Excel .PARAMETER ExcelPackage The Excel package imported from a file .PARAMETER WorksheetName Name of the sheet within the Excel package .PARAMETER StartRow Number of the row where import starts .PARAMETER EndRow Number of the row where import ends .PARAMETER StartColumn Number of the column where import starts .PARAMETER EndColumn Number of the column where import ends .EXAMPLE PS C:\> Import-Excel -ExcelPackage $excelPackage -WorksheetName "Sheet1" -StartRow 1 -EndRow 10 -StartColumn 1 -EndColumn 5 Imports data from $excelPackage .NOTES Derived function from PSModule "ImportExcel" by Douglas Finke Due to the fact, that I don't need the whole function of the module and want to avoid module dependencies, I've adopted and cut the function down to my own need the WinEventLogCustomization module. .LINK https://github.com/dfinke/ImportExcel #> [CmdLetBinding()] param ( [Parameter(Mandatory = $true)] [OfficeOpenXml.ExcelPackage] $ExcelPackage, [ValidateNotNullOrEmpty()] [String] $WorksheetName, [Int] $StartRow = 1, [Int] $EndRow, [Int] $StartColumn = 1, [Int] $EndColumn ) begin { # Helper function function Get-PropertyNames { <# .SYNOPSIS Create objects containing the column number and the column name for each of the different header types. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = "Name would be incorrect, and command is not exported")] param( [Parameter(Mandatory = $true)] $Sheet, [Parameter(Mandatory = $true)] [Int[]] $Columns, [Parameter(Mandatory = $true)] [Int] $StartRow ) if ($StartRow -lt 1) { Stop-PSFFunction -Message 'The top row can never be less than 1 when we need to retrieve headers from the worksheet.' -EnableException $true return } try { foreach ($column in $Columns) { $Sheet.Cells[$StartRow, $column] | Where-Object { -not [string]::IsNullOrEmpty($_.Value) } | Select-Object @{N = 'Column'; E = { $column } }, Value } } catch { Stop-PSFFunction -Message "Failed creating property names: $_" -EnableException $true return } } } process { try { $sheet = $ExcelPackage.Workbook.Worksheets[$WorksheetName] if (-not $sheet) { Stop-PSFFunction -Message "Worksheet '$WorksheetName' not found" -EnableException return } #region Get rows and columns if (-not $EndRow ) { $EndRow = $sheet.Dimension.End.Row } if (-not $EndColumn) { $EndColumn = $sheet.Dimension.End.Column } $Columns = $StartColumn .. $EndColumn if ($StartColumn -gt $EndColumn) { Write-PSFMessage -Level Warning -Message "Selecting columns $StartColumn to $EndColumn might give odd results." } $rows = (1 + $StartRow) .. $EndRow if ($StartRow -eq 1 -and $EndRow -eq 1) { $rows = 0 } #endregion Get rows and columns #region Create property names if ((-not $Columns) -or (-not ($PropertyNames = Get-PropertyNames -Sheet $sheet -Columns $Columns -StartRow $StartRow))) { Write-PSFMessage -Level Error -Message "No column headers found on top row '$StartRow'." return } if ($Duplicates = $PropertyNames | Group-Object Value | Where-Object Count -GE 2) { Stop-PSFFunction -Message "Duplicate column headers found on row '$StartRow' in columns '$($Duplicates.Group.Column)'. Column headers must be unique." -EnableException $true return } #endregion if (-not $rows) { Write-PSFMessage -Level Warning -Message "Worksheet '$WorksheetName' contains no data in the rows after top row '$StartRow'" } else { # Create one object per row foreach ($row in $rows) { Write-PSFMessage -Level Debug -Message "Import row '$row'" $NewRow = [Ordered]@{} foreach ($propertyName in $PropertyNames) { $NewRow[$propertyName.Value] = $sheet.Cells[$row, $propertyName.Column].Value } $NewRow } } } catch { Stop-PSFFunction -Message "Failed importing the Excel workbook. $_" -EnableException $true return } } end { } } function Get-WELCEventChannel { <# .Synopsis Get-WELCEventChannel .DESCRIPTION Query Windows Eventlog Channel(s) and their provider information. .PARAMETER ComputerName The computer(s) to connect to. Supports PSSession objects also. .PARAMETER Session A PSSession object for remote connection to another machine .PARAMETER Credential The credentials to use on remote calls .PARAMETER ChannelFullName The name of the EventChannel to query Default is every channel .EXAMPLE PS C:\> Get-WELCEventChannel Display all available subscription .EXAMPLE PS C:\> Get-WELCEventChannel -ChannelFullName MyChannel Display Channel "MyChannel" .EXAMPLE PS C:\> Get-WELCEventChannel -ChannelFullName MyChannel -ComputerName SRV01 Display Channel "MyChannel" from remote computer "SRV01". .EXAMPLE PS C:\> Get-WELCEventChannel -ChannelFullName MyChannel -Sesion $PSSession Display Channel "MyChannel" from all connections within the $PSSession variable Assuming $PSSession variable is created something like this: $PSSession = New-PSSession -ComputerName SRV01 .NOTES Author: Andreas Bellstedt .LINK https://github.com/AndiBellstedt/WinEventLogCustomization #> [CmdletBinding( DefaultParameterSetName = 'ComputerName', PositionalBinding = $true, ConfirmImpact = 'low' )] [OutputType([WELC.EventLogChannel])] Param( [Parameter( ValueFromPipeline = $true, Position = 0 )] [Alias("Name", "ChannelName", "LogName")] [ValidateNotNullOrEmpty()] [String[]] $ChannelFullName = "*", [Parameter( ParameterSetName = "ComputerName", ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 1 )] [Alias("Host", "Hostname", "Computer", "DNSHostName")] [PSFComputer[]] $ComputerName = $env:COMPUTERNAME, [Parameter( ParameterSetName = "Session", Position = 1 )] [System.Management.Automation.Runspaces.PSSession[]] $Session, [Parameter( ParameterSetName = "ComputerName" )] [PSCredential] $Credential ) begin { # If session parameter is used -> transfer it to ComputerName, # The class "PSFComputer" from PSFramework can handle it. This simplifies the handling in the further process block if ($Session) { $ComputerName = $Session.ComputerName } $channelFullNameBound = Test-PSFParameterBinding -ParameterName ChannelFullName $computerBound = Test-PSFParameterBinding -ParameterName ComputerName } process { #region parameterset workarround Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($PsCmdlet.ParameterSetName)" # Workarround parameter binding behaviour of powershell in combination with ComputerName Piping if (-not ($channelFullNameBound -or $computerBound) -and $ComputerName.InputObject -and $PSCmdlet.ParameterSetName -ne "Session") { if ($ComputerName.InputObject -is [string]) { $ComputerName = $env:ComputerName } else { $ChannelFullName = "*" } } #endregion parameterset workarround #region Processing Channels foreach ($computer in $ComputerName) { $winEventProviders = @() $errorChannel = @() foreach ($channel in $ChannelFullName) { $ErrorReturn = $null $paramInvokeCmd = [ordered]@{ "ComputerName" = $computer "ErrorAction" = "Stop" ErrorVariable = "ErrorReturn" "ArgumentList" = $channel } if ($PSCmdlet.ParameterSetName -eq "Session") { $paramInvokeCmd['ComputerName'] = $Session } if ($Credential) { $paramInvokeCmd.Add("Credential", $Credential) } Write-PSFMessage -Level Verbose -Message "Query EventLog channel '$($channel)' on computer '$($computer)'" -Target $computer try { $winEventChannels = Invoke-PSFCommand @paramInvokeCmd -ScriptBlock { Get-WinEvent -ListLog $args[0] -ErrorAction Stop } } catch { Stop-PSFFunction -Message "Unable to query EventLog channel '$($channel)' on computer '$($computer)'. ErrorMessage: $($ErrorReturn.Exception.Message | Select-Object -Unique)" -Target $computer -ErrorRecord $_ continue } [array]$providerNames = $winEventChannels.ProviderNames | Select-Object -Unique Write-PSFMessage -Level Verbose -Message "Query $($providerNames.count) provider from EventLog channel '$($winEventChannels.LogName)'" -Target $computer $errorMessages = @() $providerNames = $providerNames | Where-Object { $_ -notin $winEventProviders.name -and $_ -notin $errorChannel } $winEventProviders += foreach ($providerName in $providerNames) { $ErrorReturn = $null $paramInvokeCmd['ArgumentList'] = $providerName try { Invoke-PSFCommand @paramInvokeCmd -ScriptBlock { Get-WinEvent -ListProvider $args -ErrorAction SilentlyContinue -ErrorVariable ErrorOccured if ($ErrorOccured) { Write-Error -Message ([string]::Join(" " , ($ErrorOccured.Exception.Message | Select-Object -Unique))) -ErrorAction Stop } } -Verbose:$false } catch { Write-PSFMessage -Level Debug -Message "Unable to query provider '$($providerName)' from computer '$($computer)'. ErrorMessage: $($ErrorReturn.Exception | Select-Object -ExpandProperty Message -Unique)" -Target $computer -ErrorRecord $_ $errorChannel += $providerName $errorMessages += $ErrorReturn.Exception[-1].Message } } if ($errorMessages) { Write-PSFMessage -Level Error -Message "Error query provider ($([string]::Join(", ", $providerNames))) from EventLog Channel '$($winEventChannels.LogName)' on computer '$($computer)'. ErrorMessage: $([string]::join(" ", $errorMessages))" -Target $computer } Write-PSFMessage -Level Verbose -Message "Output $(([Array]$winEventChannels).count) EventLog channel$(if(([Array]$winEventChannels).count -gt 1){"s"})" -Target $computer # Output result foreach ($winEventChannel in $winEventChannels) { $output = [WELC.EventLogChannel]@{ "PSComputerName" = $computer "WinEventLog" = $winEventChannel "Provider" = ( $winEventChannel.ProviderNames | ForEach-Object { $_name = $_; $winEventProviders | Where-Object ProviderName -like $_name } ) } $output } } } #endregion Processing Events } end { } } function Import-WELCChannelDefinition { <# .Synopsis Import-WELCChannelDefinition .DESCRIPTION Import definition data for creating custom Windows EventLog Channels from a Excel file The Excel file acts as a definition database and provide easy handling and definition for custom eventlog channels and there structure Additionally in the excel file, there is the possibility to manage XPath-Queries for Windows Event Forwading queries .PARAMETER Path The Excel file or a folder with Excel files to import .PARAMETER Sheet The Name of the sheet within the Excel file .PARAMETER Table The table containing the definition data within the sheet of the Excel file .PARAMETER FileExtension A list of file extensions indicating Excel files Only needed/used if a folder is specified as a Path .PARAMETER Recursive The specified path will be parsed recursivly Only needed/used if a folder is specified as a Path .PARAMETER OutputChannelDefinition If specified the function will output a WELC.ChannelDefinition object, instead of WELC.TemplateRecord data .PARAMETER OutputChannelConfig If specified the function will output a WELC.ChannelDefinition object, instead of WELC.TemplateRecord data .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .EXAMPLE PS C:\> Import-WELCChannelDefinition -Path C:\WELC\MyFile.xls Import the excel file 'C:\WELC\MyFile.xls' with the default expected parametersettings (Excel file has to contain a Sheet 'CustomEventLogChannels' and a table 'T_Channel') .EXAMPLE PS C:\> Import-WELCChannelDefinition -Path C:\WELC Import all excel files in path 'C:\WELC' with the default expected parametersettings (sheet and table settings like in first example. Files have to have an extension with ".xlsx", ".xlsm", ".xls") .EXAMPLE PS C:\> Import-WELCChannelDefinition -Path C:\WELC -Recursive -FileExtension "xlsx", "xlsm", "xls" Import all excel files in path 'C:\WELC' and in all subfolders with the specified extensions "xlsx", "xlsm", "xls" (sheet and table settings like in first example) .EXAMPLE PS C:\> Import-WELCChannelDefinition -Path C:\WELC\MyFile.xls -Sheet "CustomEventLogChannels" -Table "T_Channel" Import the excel file 'C:\WELC\MyFile.xls' with the explicit parameter settings on sheet and table .NOTES Author: Andreas Bellstedt .LINK https://github.com/AndiBellstedt/WinEventLogCustomization #> [CmdLetBinding( DefaultParameterSetName = "OutputTemplateRecord", SupportsShouldProcess = $true, PositionalBinding = $true, ConfirmImpact = 'Low' )] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Intentional')] [OutputType("WELC.ChannelDefinition", "WELC.ChannelConfig", "WELC.TemplateRecord", "PSObject")] param( [parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0 )] [Alias("FullName", "FilePath", "Folder", "File")] [string[]] $Path, [ValidateNotNullOrEmpty()] [Alias("ExcelSheet", "SheetName")] [string] $Sheet = "CustomEventLogChannels", [ValidateNotNullOrEmpty()] [Alias("ExcelTable", "TableName")] [string] $Table = "T_Channel", [string[]] $FileExtension = @("xlsx", "xlsm", "xls"), [switch] $Recursive, [Parameter(ParameterSetName = "OutputChannelDefinition" )] [switch] $OutputChannelDefinition, [Parameter(ParameterSetName = "OutputChannelConfig" )] [switch] $OutputChannelConfig ) begin { # ensure correct format for specified extensions if ($FileExtension) { $FileExtension = foreach ($item in $FileExtension) { $item = $item.Trim(".") ".$($item)" } } } process { Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($PsCmdlet.ParameterSetName)" # working trough the specified path(s) foreach ($pathItem in $Path) { # File and folder validity tests if (Test-Path -Path $pathItem -PathType Leaf) { Write-PSFMessage -Level Verbose -Message "Found file '$($pathItem)' as a valid file in path" $files = $pathItem | Resolve-Path | Get-ChildItem | Select-Object -ExpandProperty FullName } elseif (Test-Path -Path $pathItem -PathType Container) { Write-PSFMessage -Level Verbose -Message "Getting files in path '$($pathItem)'" $param = @{ Path = $pathItem "File" = $true } if ($Recursive) { $param["Recursive"] = $true } $files = Get-ChildItem @param | Where-Object Extension -in $FileExtension | Select-Object -ExpandProperty FullName Write-PSFMessage -Level Verbose -Message "Found $($files.count) file$(if($files.count -gt 1){"s"}) in path " } elseif (-not (Test-Path -Path $pathItem -PathType Any -IsValid)) { Write-PSFMessage -Level Error -Message "'$pathItem' is not a valid path or file." continue } else { Write-PSFMessage -Level Error -Message "Unable to open '$($pathItem)'" continue } # Working trough the actual found file(s) foreach ($file in $files) { Write-PSFMessage -Level Verbose -Message "Open file '$($file)' as Excel file" # Open the file #$excelDocument = Get-ExcelDocument -Path $file $excelDocument = New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $file if (-not $excelDocument) { continue } # Select the specified sheet $excelSheet = $excelDocument.Workbook.Worksheets | Where-Object name -like $Sheet if (-not $excelSheet) { Write-PSFMessage -Level Error -Message "Excel file '$($file.split("\")[-1])' contains no sheet '$($Sheet)'" continue } # Select the specified table $excelTable = $excelSheet.Tables | Where-Object name -like $Table if (-not $table) { Write-PSFMessage -Level Error -Message "Unable to find table '$($Table)' in sheet '$($file.split("\")[-1])' " continue } # Prepare importing the table as powershell object $param = @{ ExcelPackage = $excelDocument WorksheetName = $excelSheet.name StartRow = $excelTable.Address.Start.Row StartColumn = $excelTable.Address.Start.Column EndRow = $excelTable.Address.End.Row EndColumn = $excelTable.Address.End.Column } if ($pscmdlet.ShouldProcess("table '$($Table)' in sheet '$($Sheet)' from file '$($file)'", "Import")) { Write-PSFMessage -Level Debug -Message "Import Excel file" # Import and filter data from excel table into powershell $tableData = Import-Excel @param $data = $tableData | Where-Object LogFullName Write-PSFMessage -Level Verbose -Message "Found $(([array]$data).Count) usable records in $($tableData.Count) records from table '$($Table)' in worksheet '$($Sheet)'" # Output result foreach ($item in $data) { switch ($pscmdlet.ParameterSetName) { "OutputChannelDefinition" { if ($OutputChannelDefinition) { $output = [WELC.ChannelDefinition]@{ ChannelName = $item.ChannelName ChannelSymbol = $item.ChannelSymbol ProviderName = $item.ProviderName ProviderSymbol = $item.ProviderSymbol } $output } } "OutputChannelConfig" { if ($OutputChannelConfig) { $output = [WELC.ChannelConfig]@{ ChannelName = $item.ChannelName LogFullName = $item.LogFullName LogMode = $item.LogMode Enabled = [bool]::Parse($item.ChannelEnabled) MaxEventLogSize = $item.MaxEventLogSize / 1 } $output } } "OutputTemplateRecord" { $item.psobject.TypeNames.Insert(0, "WELC.TemplateRecord") $item } Default { Stop-PSFFunction -Message "Unhandeled ParameterSetName. Developers mistake." -EnableException $true throw } } } } # Data/variable cleanup Write-PSFMessage -Level Debug -Message "Close Excel file and cleanup variables" $excelSheet.Dispose() $excelDocument.Dispose() Remove-Variable excelDocument, excelSheet, excelTable, tableData, data, param, item -Force -Confirm:$false -ErrorAction:Ignore } Remove-Variable file, files -Force -Confirm:$false -ErrorAction Ignore } Remove-Variable pathItem -Force -Confirm:$false -ErrorAction Ignore } end { } } function Move-WELCEventChannelManifest { <# .SYNOPSIS Move-WELCEventChannelManifest .DESCRIPTION Move a manifest with the compiled DLL file from a source to destination directory The manifest has to be rewritten to fit the destination path otherwise, there will be errors when registering/usering it .PARAMETER Path The path to the manifest file You can specify the fullname of the manifest-file (.man), or just the directory In case of a directory, all the manifest files in the directory will be processed .PARAMETER DestinationPath The path where to store the manifest and DLL file .PARAMETER Prepare The rewrite of the manifest will be done, but the files will not be moved .PARAMETER CopyMode Copy the files from the source to the destination, instead of moving them .PARAMETER PassThru The moved files will be parsed to the pipeline for further processing. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .NOTES Author: Andreas Bellstedt .LINK https://github.com/AndiBellstedt/WinEventLogCustomization .EXAMPLE PS C:\> Move-WELCEventChannelManifest -Path C:\CustomDLLPath\MyChannel.man -DestinationPath $env:WinDir\System32 The manifest and its DLL file will be copied to the system32 directory of the current windows installation .EXAMPLE PS C:\> Move-WELCEventChannelManifest -Path C:\CustomDLLPath -DestinationPath $env:WinDir\System32 All manifest files will copied over to the system32 directory. .EXAMPLE PS C:\> Move-WELCEventChannelManifest -Path C:\CustomDLLPath\MyChannel.dll -DestinationPath $env:WinDir\System32 -Prepare The manifest will rewritten to the destination folder, but the actual file will not be moved #> [CmdletBinding( SupportsShouldProcess = $true, PositionalBinding = $true, ConfirmImpact = 'Medium' )] param ( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [Alias("File", "FileName", "FullName")] [String[]] $Path, [Parameter( Mandatory = $true, Position = 1 )] [String] $DestinationPath, [switch] $Prepare, [switch] $CopyMode, [switch] $PassThru ) begin { $DestinationPath = $DestinationPath.TrimEnd("\") $DestinationPath = $DestinationPath | Resolve-Path | Get-Item | Select-Object -ExpandProperty FullName } process { $files = @() foreach ($pathItem in $Path) { # File and folder validity tests if (Test-Path -Path $pathItem -PathType Leaf) { Write-PSFMessage -Level Verbose -Message "Found file '$($pathItem)' as a valid file in path" -Target $env:COMPUTERNAME $files = $pathItem | Resolve-Path | Get-ChildItem | Select-Object -ExpandProperty FullName } elseif (Test-Path -Path $pathItem -PathType Container) { Write-PSFMessage -Level Verbose -Message "Getting files in path '$($pathItem)'" -Target $env:COMPUTERNAME $files = Get-ChildItem -Path $pathItem -File -Filter "*.man" | Select-Object -ExpandProperty FullName Write-PSFMessage -Level Verbose -Message "Found $($files.count) file$(if($files.count -gt 1){"s"}) in path" -Target $env:COMPUTERNAME if (-not $files) { Write-PSFMessage -Level Warning -Message "No manifest files found in path '$($pathItem)'" -Target $env:COMPUTERNAME } } elseif (-not (Test-Path -Path $pathItem -PathType Any -IsValid)) { Write-PSFMessage -Level Error -Message "'$pathItem' is not a valid path or file." -Target $env:COMPUTERNAME continue } else { Write-PSFMessage -Level Error -Message "unable to open '$($pathItem)'" -Target $env:COMPUTERNAME continue } foreach ($file in $files) { # open XML file Write-PSFMessage -Level Verbose -Message "Opening XML manifest file '$($file)' to gather DLL information" $xmlfile = New-Object XML $xmlfile.Load($file) if ( $xmlfile.instrumentationManifest.schemaLocation -eq "http://schemas.microsoft.com/win/2004/08/events eventman.xsd" -and $xmlfile.instrumentationManifest.xmlns -eq "http://schemas.microsoft.com/win/2004/08/events" -and $xmlfile.instrumentationManifest.win -eq "http://manifests.microsoft.com/win/2004/08/windows/events" -and $xmlfile.instrumentationManifest.xsi -eq "http://www.w3.org/2001/XMLSchema-instance" -and $xmlfile.instrumentationManifest.xs -eq "http://www.w3.org/2001/XMLSchema" -and $xmlfile.instrumentationManifest.trace -eq "http://schemas.microsoft.com/win/2004/08/events/trace" ) { # Gather files and rewrite XML $manifestFolder = Split-Path -Parent $file Write-PSFMessage -Level Debug -Message "Gather path of resourceFileName DLL" $resourceFileNameFullName = $xmlfile.instrumentationManifest.instrumentation.events.provider.resourceFileName $resourceFileNamePath = Split-Path -Path $resourceFileNameFullName $resourceFileNameFile = Split-Path -Path $resourceFileNameFullName -Leaf if ($DestinationPath -like $resourceFileNamePath) { Write-PSFMessage -Level Significant -Message "Source and destination path of ressource file '$resourceFileNameFullName' are the same. Nothing to do" } else { $destResourceFileName = "$($DestinationPath)\$($resourceFileNameFile)" $xmlfile.instrumentationManifest.instrumentation.events.provider.resourceFileName = $destResourceFileName Write-PSFMessage -Level Verbose -Message "Rewrite path of messageFileName DLL from '$($resourceFileNameFullName)' to '$($destResourceFileName)'" if (Test-Path -Path $destResourceFileName -PathType Leaf) { # DLL is already present in destination directory $resourceFileNameFullName = $destResourceFileName } elseif (Test-Path -Path "$($manifestFolder)\$($resourceFileNameFile)" -PathType Leaf) { # DLL path in XML is wrong, but DLL is next to manifest file $resourceFileNameFullName = "$($manifestFolder)\$($resourceFileNameFile)" } elseif (Test-Path -Path $resourceFileNameFullName -PathType Leaf) { # nothing to do, DLL is in path from xml file, but has to be mnoved somewhere different } else { Stop-PSFFunction -Message "Ressource file '$($resourceFileNameFile)' not found. Searched in folders: '$($resourceFileNamePath)', '$($manifestFolder)', '$($DestinationPath)'" -EnableException $true } } Write-PSFMessage -Level Debug -Message "Gather path of messageFileName DLL" $messageFileNameFullName = $xmlfile.instrumentationManifest.instrumentation.events.provider.messageFileName $messageFileNamePath = Split-Path -Path $messageFileNameFullName $messageFileNameFile = Split-Path -Path $messageFileNameFullName -Leaf if ($DestinationPath -like $messageFileNamePath) { Write-PSFMessage -Level Verbose -Message "Source and destination path of message file '$($messageFileNameFullName)' are the same. Nothing to do" } else { $destMessageFileName = "$($DestinationPath)\$($messageFileNameFile)" $xmlfile.instrumentationManifest.instrumentation.events.provider.messageFileName = $destMessageFileName Write-PSFMessage -Level Verbose -Message "Rewrite path of messageFileName DLL from '$($messageFileNameFullName)' to '$($destMessageFileName)'" if (Test-Path -Path $destMessageFileName -PathType Leaf) { # DLL is already present in destination directory $messageFileNameFullName = $destMessageFileName } elseif (Test-Path -Path "$($manifestFolder)\$($messageFileNameFile)" -PathType Leaf) { # DLL path in XML is wrong, but DLL is next to manifest file $messageFileNameFullName = "$($manifestFolder)\$($messageFileNameFile)" } elseif (Test-Path -Path $messageFileNameFullName -PathType Leaf) { # nothing to do, DLL is in path from xml file, but has to be mnoved somewhere different } else { Stop-PSFFunction -Message "Message file '$($messageFileNameFile)' not found. Searched in folders: '$($messageFileNamePath)', '$($manifestFolder)', '$($DestinationPath)'" -EnableException $true } } Write-PSFMessage -Level Debug -Message "Gather path of parameterFileName DLL" $parameterFileNameFullName = $xmlfile.instrumentationManifest.instrumentation.events.provider.parameterFileName $parameterFileNamePath = Split-Path -Path $parameterFileNameFullName $parameterFileNameFile = Split-Path -Path $parameterFileNameFullName -Leaf if ($DestinationPath -like $parameterFileNamePath) { Write-PSFMessage -Level Verbose -Message "Source and destination path of parameter file '$($parameterFileNameFullName)' are the same. Nothing to do" } else { $destParameterFileName = "$($DestinationPath)\$($parameterFileNameFile)" $xmlfile.instrumentationManifest.instrumentation.events.provider.parameterFileName = $destParameterFileName Write-PSFMessage -Level Verbose -Message "Rewrite path of parameterFileName DLL from '$($parameterFileNameFullName)' to '$($destParameterFileName)'" if (Test-Path -Path $destParameterFileName -PathType Leaf) { # DLL is already present in destination directory $parameterFileNameFullName = $destParameterFileName } elseif (Test-Path -Path "$($manifestFolder)\$($parameterFileNameFile)" -PathType Leaf) { # DLL path in XML is wrong, but DLL is next to manifest file $parameterFileNameFullName = "$($manifestFolder)\$($parameterFileNameFile)" } elseif (Test-Path -Path $parameterFileNameFullName -PathType Leaf) { # nothing to do, DLL is in path from xml file, but has to be mnoved somewhere different } else { Stop-PSFFunction -Message "Parameter file '$($parameterFileNameFile)' not found. Searched in folders: '$($parameterFileNamePath)', '$($manifestFolder)', '$($DestinationPath)'" -EnableException $true } } } else { Stop-PSFFunction -Message "$($file) is not a actual manifest file" -EnableException $true } if ($pscmdlet.ShouldProcess("file '$($file)' with directory '$($DestinationPath)'", "Set")) { $xmlfile.Save($file) Write-PSFMessage -Level Verbose -Message "Save file '$($file)' in directory '$($DestinationPath)'" } if (-not $Prepare -or $CopyMode) { Write-PSFMessage -Level Verbose -Message "Copy/Move manifest and DLL files into directory '$($DestinationPath)'" if ($pscmdlet.ShouldProcess("File manifest '$($file)' to '$($DestinationPath)'$(if($CopyMode){"in CopyMode"})", "Move")) { if ($CopyMode) { Write-PSFMessage -Level Debug -Message "Copy manifest file" $destfile = Copy-Item -Path $file -Destination $DestinationPath -Force -PassThru } else { Write-PSFMessage -Level Debug -Message "Move manifest file" $destfile = Move-Item -Path $file -Destination $DestinationPath -Force -PassThru } } if ($pscmdlet.ShouldProcess("Dll file '$($resourceFileNameFullName)' to '$($DestinationPath)'$(if($CopyMode){"in CopyMode"})", "Move")) { if ($CopyMode) { Write-PSFMessage -Level Debug -Message "Copy resourceFileName dll file" Copy-Item -Path $resourceFileNameFullName -Destination $DestinationPath -Force } else { Write-PSFMessage -Level Debug -Message "Move resourceFileName dll file" Move-Item -Path $resourceFileNameFullName -Destination $DestinationPath -Force } } if ($messageFileNameFullName -notlike $resourceFileNameFullName) { if ($pscmdlet.ShouldProcess("File message dll file '$($messageFileNameFullName)' to '$($DestinationPath)'$(if($CopyMode){"in CopyMode"})", "Move")) { if ($CopyMode) { Write-PSFMessage -Level Debug -Message "Copy messageFileName dll file" Copy-Item -Path $messageFileNameFullName -Destination $DestinationPath -Force } else { Write-PSFMessage -Level Debug -Message "Move messageFileName dll file" Move-Item -Path $messageFileNameFullName -Destination $DestinationPath -Force } } } if ($parameterFileNameFullName -notlike $resourceFileNameFullName) { if ($pscmdlet.ShouldProcess("File parameter dll file '$($parameterFileNameFullName)' to '$($DestinationPath)'$(if($CopyMode){"in CopyMode"})", "Move")) { if ($CopyMode) { Write-PSFMessage -Level Debug -Message "Copy parameterFileName dll file" Copy-Item -Path $parameterFileNameFullName -Destination $DestinationPath -Force } else { Write-PSFMessage -Level Debug -Message "Move parameterFileName dll file" Move-Item -Path $parameterFileNameFullName -Destination $DestinationPath -Force } } } if ($PassThru) { Write-PSFMessage -Level Verbose -Message "PassThru mode, outputting manifest file" $destfile } } else { if ($PassThru) { Write-PSFMessage -Level Verbose -Message "PassThru mode, outputting manifest file" $file | Get-Item } } } } } end { } } function New-WELCEventChannelManifest { <# .SYNOPSIS New-WELCEventChannelManifest Creates Manifest- and DLL-file for register custom Windows EventLog Channels .DESCRIPTION Creates Manifest- and DLL-file for register custom Windows EventLog Channels Once compiled, the files can be registered into a Windows EventLog system to allow custom logs. .PARAMETER InputObject WELC.Channeldefinition object to create manifest and the dll file from Can be piped in with somthing like: Import-WELCChannelDefinition -Path C:\EventLogs\WinEventLogCustomization.xlsx -OutputChannelDefinition Excel template file can be created/opened with Open-WELCExcelTemplate .PARAMETER ChannelFullName The full name for a EventChannel. Valid Formats are: "FolderRoot/ChannelName" "FolderRoot/SubFolder1/SubFolder2/ChannelName" String have to be specified correctly with slashes ("/"), NOT with backslash ("\")! .PARAMETER FolderRoot Name of the folder within the Windows EventLog System under "Application and Services" .PARAMETER FolderSecondLevel Name of the folder within the "FolderRoot" .PARAMETER FolderThirdLevel Name of the folder within the "FolderSecondLevel" .PARAMETER ChannelName Name of the EventChannel within it's folder .PARAMETER ChannelSymbol Name of the ChannelSymbol Optional. If not specified, the name will be derived from the ChannelName .PARAMETER ProviderName Name of the EventLog Provider Optional. If not specified, the name will be derived from the folder name(s) .PARAMETER ProviderSymbol Name of the ProviderSymbol Optional. If not specified, the name will be derived from the ProviderName .PARAMETER OutputFile The filename of the manifest to create. Please specify only a FILE name, not a full qualified path If not specified, the name will be derived from the ChannelFullName/ ChannelName .PARAMETER DestinationPath Output path for manifest- and the dll file to register an EventChannel within the Windows EventLog system .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .NOTES Author: Andreas Bellstedt Adopted from Russell Tomkins "Project Sauron" Author: Russell Tomkins Github: https://www.github.com/russelltomkins/ProjectSauron Originbal description: --------------------- Name: Create-Manifest.ps1 Version: 1.1 Author: Russell Tomkins - Microsoft Premier Field Engineer Blog: https://aka.ms/russellt Refer to this blog series for more details http://blogs.technet.microsoft.com/russellt/2017/03/23/project-sauron-part-1 .LINK https://github.com/AndiBellstedt/WinEventLogCustomization .EXAMPLE PS C:\> New-WELCEventChannelManifest -InputObject $ChannelDefinition Creates the manfifest- and DLL-file to register a custom EventLog channel in Windows Output depend on content in Excel file. Each root channel will be a manifest- (,man) and a DLL-file. Assuming that the variable $ChannelDefinition contains a WELC.ChannelDefinition object(list) PS C:\> $ChannelDefinition = Import-WELCChannelDefinition -Path CustomEventLogChannel.xlsx .EXAMPLE PS C:\> Import-WELCChannelDefinition -Path CustomEventLogChannel.xlsx | New-WELCEventChannelManifest Creates the Manfifest file and compile dll file(s) from the content of 'CustomEventLogChannel.xlsx' .EXAMPLE PS C:\> New-WELCEventChannelManifest -ChannelFullName "ChannelFolder/ChannelName" Creates a manifest (ChannelFolder.man) and compile a dll file (ChannelFolder.dll) with a single EventLogChannel "ChannelName" and a folder "ChannelFolder" .EXAMPLE PS C:\> "MyFolder/MyChannel1", "MyFolder/MyChannel2", "MyFolder/MyChannel3", "MyFolder/MyChannel4" | New-WELCEventChannelManifest Creates a manifest (MyChannel.man) and compile a dll file (MyChannel.dll) with 4 EventLogChannels MyChannel1-4 in the folder "MyFolder" #> [CmdletBinding( SupportsShouldProcess = $true, PositionalBinding = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'ManualDefinition' )] Param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = "InputObject", Position = 0 )] [ValidateNotNullOrEmpty()] [Alias("Object", "In", "ChannelDefinition")] [WELC.ChannelDefinition[]] $InputObject, [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "ManualFullChannelName", Position = 0 )] [ValidateNotNullOrEmpty()] [Alias("FullName")] [String] $ChannelFullName, [Parameter(ParameterSetName = "ManualFullChannelName")] [String] $ChannelSymbol, [Parameter(ParameterSetName = "ManualFullChannelName")] [String] $ProviderName, [Parameter(ParameterSetName = "ManualFullChannelName")] [String] $ProviderSymbol, [Parameter( Mandatory = $true, ParameterSetName = "ManualDefinition", Position = 0 )] [ValidateNotNullOrEmpty()] [Alias("Root", "FolderNameRoot", "RootFolderName")] [String] $FolderRoot, [Parameter( Mandatory = $false, ParameterSetName = "ManualDefinition", Position = 1 )] [ValidateNotNullOrEmpty()] [Alias("SecondLevel", "FolderNameSecondLevel", "SecondLevelFolderName")] [String] $FolderSecondLevel, [Parameter( Mandatory = $false, ParameterSetName = "ManualDefinition", Position = 2 )] [ValidateNotNullOrEmpty()] [Alias("ThirdLevel", "FolderNameThirdLevel", "ThirdLevelFolderName")] [String] $FolderThirdLevel, [Parameter( Mandatory = $true, ParameterSetName = "ManualDefinition", Position = 3 )] [ValidateNotNullOrEmpty()] [String] $ChannelName, [Alias("FileName")] [String] $OutputFile, [ValidateNotNullOrEmpty()] [Alias("OutputPath")] [String] $DestinationPath = ".\" ) Begin { #region Constants Write-PSFMessage -Level Debug -Message "Initalizing constants" # Path to csc.exe in windows [String]$WindowsCSCPath = "$($env:windir)\Microsoft.NET\Framework64\v4.0.30319" Write-PSFMessage -Level Debug -Message ".NET Framework in '$($WindowsCSCPath)'" # Compilation tools from the windows SDK. The required executables are "mc.exe", "rc.exe" and "rcdll.dll". There is another tool "ecmangen.exe" (EventChannel ManifestGenerator) which is usefull to check and maintain the manifest files. [String]$CompilationToolPath = "$($MyInvocation.MyCommand.Module.ModuleBase)\bin" Write-PSFMessage -Level Debug -Message "Binary CompilationTool is in '$($CompilationToolPath)'" # Path where the output files, and some other temp files from the compilation process are stored. [String]$TempPath = "$($env:TEMP)\WELC_$([guid]::NewGuid().guid)" Write-PSFMessage -Level Debug -Message "Operating in temporary path '$($TempPath)'" #endregion Constants #region Variables $channelDefinitions = @() #endregion Variables #region Validity checks Write-PSFMessage -Level Debug -Message "Initial parameter validation" # Check for required resscoures und compilation folder if ($DestinationPath.EndsWith('\')) { $DestinationPath = $DestinationPath.TrimEnd('\') } $DestinationPath = Resolve-Path $DestinationPath -ErrorAction Stop | Select-Object -ExpandProperty Path # Check for temp folder if ($TempPath.EndsWith('\')) { $TempPath = $TempPath.TrimEnd('\') } if (Test-Path -Path $TempPath -IsValid) { if (-not (Test-Path -Path $TempPath -PathType Container)) { Write-PSFMessage -Level Debug -Message "Creating temporary directory '$($TempPath)'" New-Item -Path $TempPath -ItemType Directory -Force | Out-Null $TempPath = Resolve-Path $TempPath -ErrorAction Stop | Select-Object -ExpandProperty Path } } else { throw "$($TempPath) is not a valid path" } # Check for required resscoures und compilation folder if ($CompilationToolPath.EndsWith('\')) { $CompilationToolPath = $CompilationToolPath.TrimEnd('\') } Resolve-Path $CompilationToolPath -ErrorAction Stop | Out-Null Test-Path -Path "$($CompilationToolPath)\mc.exe" -ErrorAction Stop | Out-Null Test-Path -Path "$($CompilationToolPath)\rc.exe" -ErrorAction Stop | Out-Null Test-Path -Path "$($CompilationToolPath)\rcdll.dll" -ErrorAction Stop | Out-Null Write-PSFMessage -Level Debug -Message "Binary tools found in CompilationTool path '$($CompilationToolPath)'" #endregion Validity checks } Process { Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($PsCmdlet.ParameterSetName)" switch ($pscmdlet.ParameterSetName) { "InputObject" { $channelDefinitions += foreach ($item in $InputObject) { $item } } "ManualFullChannelName" { $channelDefinitions += foreach ($_channelFullName in $ChannelFullName) { # Validate the parameters - if SecondLevel is specified, ThirdLevel has to be present also Write-PSFMessage -Level Debug -Message "Validating ChannelFullName '$($_channelFullName)'" if ($_channelFullName -match (Get-PSFConfigValue -FullName WinEventLogCustomization.MatchString.ChannelName)) { $_channelName = $_channelFullName } else { Stop-PSFFunction -Message "Invalid format on ChannelFullName '$($_channelFullName)'. Valid format for ChannelFullName must be somthing like 'FolderRoot-FolderSecondLevel-FolderThirdLevel/ChannelName' or 'FolderRoot/ChannelName'" -EnableException $true } if (-not $ChannelSymbol) { $_channelSymbol = [String]::Join("_", $_channelFullName.Split("-").Split("/").ToUpper()) Write-PSFMessage -Level Debug -Message "ChannelSymbol not specified. Derive value '$($_channelSymbol)' from ChannelFullName" } else { if ($ChannelSymbol -match (Get-PSFConfigValue -FullName WinEventLogCustomization.MatchString.ChannelSymbol)) { $_channelSymbol = $ChannelSymbol.ToUpper() } else { Stop-PSFFunction -Message "Invalid format on ChannelSymbol '$($ChannelSymbol)'. Valid format for ChannelSymbol must be somthing like 'FolderRoot_FolderSecondLevel_FolderThirdLevel_ChannelName' or 'FolderRoot_ChannelName'" -EnableException $true } } if (-not $ProviderName) { $_providerName = $_channelFullName.Replace( "/$($_channelFullName.Split("/")[-1])", "") Write-PSFMessage -Level Debug -Message "ProviderName not specified. Derive value '$($_providerName)' from ChannelFullName" } else { if ($ProviderName -match (Get-PSFConfigValue -FullName WinEventLogCustomization.MatchString.ProviderName)) { $_providerName = $ProviderName } else { Stop-PSFFunction -Message "Invalid format on ProviderName '$($ProviderName)'. Valid format for ProviderName must be somthing like 'FolderRoot-FolderSecondLevel-FolderThirdLevel' or 'FolderRoot'" -EnableException $true } } if (-not $ProviderSymbol) { $_providerSymbol = [String]::Join("_", ($_channelFullName.Replace( "/$($_channelFullName.Split("/")[-1])", "")).Split("-").ToUpper()) Write-PSFMessage -Level Debug -Message "ChannelSymbol not specified. Derive value '$($_providerSymbol)' from ChannelFullName" } else { if ($ProviderSymbol -match (Get-PSFConfigValue -FullName WinEventLogCustomization.MatchString.ProviderSymbol)) { $_providerSymbol = $ProviderSymbol.ToUpper() } else { Stop-PSFFunction -Message "Invalid format on ProviderSymbol '$($ProviderSymbol)'. Valid Format for ProviderSymbol must be somthing like 'FolderRoot-FolderSecondLevel-FolderThirdLevel' or 'FolderRoot'" -EnableException $true } } # Create custom "WEC.ChannelDefinition" object Write-PSFMessage -Level Verbose -Message "Create ChannelDefinition object from '$($_channelFullName)'" [PSCustomObject]@{ ProviderSymbol = $_providerSymbol ProviderName = $_providerName ChannelName = $_channelName ChannelSymbol = $_channelSymbol } # Cleanup the mess of variables Write-PSFMessage -Level Debug -Message "Cleanup variables" Remove-Variable _channelSymbol, _channelName, _providerSymbol, _providerName -Force -ErrorAction Ignore -WarningAction Ignore -Verbose:$false -Confirm:$false -WhatIf:$false -Debug:$false } } "ManualDefinition" { # Validate the parameters - if SecondLevel is specified, ThirdLevel has to be present also if ($FolderSecondLevel -and ($null -eq $FolderThirdLevel)) { Write-PSFMessage -Level Warning -Message "Parameter 'FolderSecondLevel' was specified, but 'FolderThirdLevel' is missing." Write-PSFMessage -Level Warning -Message "By design, only 'FolderRoot' or all the FolderPaths has to be specified." Stop-PSFFunction -Message "Aborting creation." } # Build variables for custom ChannelDefinition object Write-PSFMessage -Level Debug -Message "Arranging data for ChannelDefinition object" [Array]$_folderNames = $FolderRoot, $FolderSecondLevel, $FolderThirdLevel | ForEach-Object { if ($_) { $_ } } [Array]$_providerSymbols = $FolderRoot.toupper(), $FolderSecondLevel.toupper(), $FolderThirdLevel.toupper() | ForEach-Object { if ($_) { $_ } } [Array]$_channelSymbols = $_providerSymbols + $ChannelName.toupper() $_providerName = [String]::Join("-", $_folderNames) $_providerSymbol = [String]::Join("_", $_providerSymbols) $_channelName = [String]::Join("-", $_folderNames) + "/" + $ChannelName $_channelSymbol = [String]::Join("_", $_channelSymbols) # Create custom "WEC.ChannelDefinition" object Write-PSFMessage -Level Verbose -Message "Create ChannelDefinition object for '$($_channelName)'" $channelDefinitions = [PSCustomObject]@{ ProviderSymbol = $_providerSymbol ProviderName = $_providerName ChannelName = $_channelName ChannelSymbol = $_channelSymbol } # Cleanup the mess of variables Write-PSFMessage -Level Debug -Message "Cleanup variables" Remove-Variable _channelSymbol, _channelName, _providerSymbol, _providerName, _channelSymbols, _providerSymbols, _folderNames -Force -ErrorAction Ignore -Confirm:$false -WhatIf:$false -Debug:$false } Default { throw "Undefined ParameterSet. Developers mistake." } } } End { Write-PSFMessage -Level Verbose -Message "Collected $($channelDefinitions.Count) channel definition$(if($channelDefinitions.Count -gt 1){"s"})" [array]$baseNames = $channelDefinitions | Select-Object -ExpandProperty ProviderName | Foreach-Object { $_.split("-")[0] } | Sort-Object -Unique Write-PSFMessage -Level Verbose -Message "Going to create $($baseNames.Count) manifest file$(if($baseNames.Count -gt 1){"s"}) from collected channel definition$(if($channelDefinitions.Count -gt 1){"s"})" foreach ($baseName in $baseNames) { # Shorten Name for file if ($pscmdlet.ParameterSetName -like "InputObject") { if ($OutputFile) { $OutputFile = $OutputFile.Replace( ".$($OutputFile.Split(".")[-1])", "") $fileName = $OutputFile + "_" + $baseName.Replace(" ", "") } else { $fileName = $baseName.Replace(" ", "") } } else { if ($OutputFile) { $fileName = $OutputFile.Replace( ".$($OutputFile.Split(".")[-1])", "") } else { $fileName = $baseName.Replace(" ", "") } } # The Resource and Message DLL that will be referenced in the manifest $fileNameDLL = $fileName + ".dll" $fullNameDLLTemp = $TempPath + "\" + $fileNameDLL $fullNameDLLDestination = $DestinationPath + "\" + $fileNameDLL # The Manifest file $fileNameManifest = $fileName + ".man" $fullNameManifestTemp = $TempPath + "\" + $fileNameManifest Write-PSFMessage -Level Verbose -Message "Arraging manifest: $($fileName) ('$($DestinationPath + "\" + $fileNameManifest)', '$($fullNameDLLDestination)')" # Filter down the the full channel list $channelSelection = $channelDefinitions | Where-Object ProviderName -like "$($baseName)*" # Extract the provider information from input $providers = $channelSelection | Select-Object -Property ProviderSymbol, ProviderName -Unique | Foreach-Object { $_ | Select-Object *, @{n = "ProviderGuid"; e = { ([guid]::NewGuid()).Guid } } } #region Create the manifest XML document Write-PSFMessage -Level Verbose -Message "Working on group '$($baseName)' with $(([array]$channelSelection).Count) channel definitions in $(([array]$providers).count) folders" #region Basic XML object definition Write-PSFMessage -Level Debug -Message "Start building manifest XML document" # Create the manifest XML document $XmlWriter = [System.XMl.XmlTextWriter]::new($fullNameManifestTemp, $null) # Set the formatting $xmlWriter.Formatting = "Indented" $xmlWriter.Indentation = "4" # Write the XML decleration $xmlWriter.WriteStartDocument() # Create instrumentation manifest $xmlWriter.WriteStartElement("instrumentationManifest") $xmlWriter.WriteAttributeString("xsi:schemaLocation", "http://schemas.microsoft.com/win/2004/08/events eventman.xsd") $xmlWriter.WriteAttributeString("xmlns", "http://schemas.microsoft.com/win/2004/08/events") $xmlWriter.WriteAttributeString("xmlns:win", "http://manifests.microsoft.com/win/2004/08/windows/events") $xmlWriter.WriteAttributeString("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") $xmlWriter.WriteAttributeString("xmlns:xs", "http://www.w3.org/2001/XMLSchema") $xmlWriter.WriteAttributeString("xmlns:trace", "http://schemas.microsoft.com/win/2004/08/events/trace") # Create instrumentation, events and provider elements $xmlWriter.WriteStartElement("instrumentation") $xmlWriter.WriteStartElement("events") #endregion Basic XML object definition foreach ($provider in $providers) { Write-PSFMessage -Level Verbose -Message "Writing provider '$($provider.ProviderName)' (GUID:$($provider.ProviderGUID))" # Start the provider $xmlWriter.WriteStartElement("provider") $xmlWriter.WriteAttributeString("name", $provider.ProviderName) $xmlWriter.WriteAttributeString("guid", "{$($provider.ProviderGUID)}") $xmlWriter.WriteAttributeString("symbol", $provider.ProviderSymbol) $xmlWriter.WriteAttributeString("resourceFileName", $fullNameDLLDestination) $xmlWriter.WriteAttributeString("messageFileName", $fullNameDLLDestination) $xmlWriter.WriteAttributeString("parameterFileName", $fullNameDLLDestination) # Start channels collection $xmlWriter.WriteStartElement("channels") [array]$channels = $channelSelection | Where-Object ProviderSymbol -eq $provider.ProviderSymbol | Select-Object -Property ChannelName, ChannelSymbol ForEach ($channelItem in $channels) { Write-PSFMessage -Level Verbose -Message "Writing channel '$($channelItem.ChannelName)'" # Start the channel $xmlWriter.WriteStartElement("channel") $xmlWriter.WriteAttributeString("name", $channelItem.ChannelName) $xmlWriter.WriteAttributeString("chid", ($channelItem.ChannelName).Replace(' ', '')) $xmlWriter.WriteAttributeString("symbol", $channelItem.ChannelSymbol) $xmlWriter.WriteAttributeString("type", "Admin") $xmlWriter.WriteAttributeString("enabled", "false") # Closing the channel $xmlWriter.WriteEndElement() } # Closing the channels $xmlWriter.WriteEndElement() # Closing the provider $xmlWriter.WriteEndElement() } #region Basic XML object definition $xmlWriter.WriteEndElement() # Closing events $xmlWriter.WriteEndElement() # Closing Instrumentation $xmlWriter.WriteEndElement() # Closing instrumentationManifest # End the XML Document $xmlWriter.WriteEndDocument() # Finish The Document $xmlWriter.Finalize $xmlWriter.Flush() $xmlWriter.Close() #endregion Basic XML object definition Write-PSFMessage -Level Verbose -Message "Manifest file '$($fileNameManifest)' has been generated ($( [math]::Round( ((Get-ChildItem -Path $fullNameManifestTemp).length / 1KB),1))KB)" #endregion Create The Manifest XML Document #region Compile the manifest to DLL Write-PSFMessage -Level Verbose -Message "Starting the compilation process on '$($fileNameDLL)'" $tempFilesExisting = @() $finalFilesExisting = @() $finalFilesExpected = @($fullNameManifestTemp, $fullNameDLLTemp) #region generates "**.h", "**.rc" and "**TEMP.BIN" file from xml manifest Write-PSFMessage -Level Debug -Message "Generate '$($fileName).h', '$($fileName).rc' and '$($fileName)TEMP.BIN' files from xml manifest" $tempFilesExpected = @("$($TempPath)\$($fileName).h", "$($TempPath)\$($fileName).rc", "$($TempPath)\$($fileName)TEMP.BIN") $tempFilesExpected | Get-ChildItem -ErrorAction SilentlyContinue | Remove-Item -Force -Confirm:$false Start-Process ` -FilePath "$($CompilationToolPath)\mc.exe" ` -ArgumentList $fullNameManifestTemp ` -WorkingDirectory $TempPath ` -NoNewWindow ` -Wait Write-PSFMessage -Level Debug -Message "Validating generated files" foreach ($tempFile in $tempFilesExpected) { if (Test-Path -Path $tempFile -NewerThan (Get-Date).AddSeconds(-5)) { $tempFilesExisting += Get-ChildItem $tempFile -ErrorAction Stop } else { Stop-PSFFunction -Message "Expected temp file '$($tempFile)' is present, but has a too old timestamp. Something went wrong. Aborting process" -EnableException $true } } Write-PSFMessage -Level Debug -Message "File generated: $([string]::Join(", ", $tempFilesExpected))" #endregion generates "**.h", "**.rc" and "**TEMP.BIN" file from xml manifest #region generates "**.cs" file from xml manifest Write-PSFMessage -Level Debug -Message "Generate '$($fileName).cs' file from xml manifest" $tempFilesExpected = @( "$($TempPath)\$($fileName).cs" ) $tempFilesExpected | Get-ChildItem -ErrorAction SilentlyContinue | Remove-Item -Force -Confirm:$false Start-Process ` -FilePath "$($CompilationToolPath)\mc.exe" ` -ArgumentList "-css NameSpace $($fullNameManifestTemp)" ` -WorkingDirectory $TempPath ` -NoNewWindow ` -Wait Write-PSFMessage -Level Debug -Message "Validating generated '$($fileName).cs' file" foreach ($tempFile in $tempFilesExpected) { if (Test-Path -Path $tempFile -NewerThan (Get-Date).AddSeconds(-5)) { $tempFilesExisting += Get-ChildItem $tempFile -ErrorAction Stop } else { Stop-PSFFunction -Message "Expected temp file '$($tempFile)' is present, but has a too old timestamp. Something went wrong. Aborting process" -EnableException $true } } Write-PSFMessage -Level Debug -Message "CS file generated: $([string]::Join(", ", $tempFilesExpected)) " #endregion generates "**.cs" file from xml manifest #region generates "**.res" file from xml manifest Write-PSFMessage -Level Debug -Message "Generate '$fileName).res' file from '$($fileName).rc' file" $tempFilesExpected = @("$($TempPath)\$($fileName).res") $tempFilesExpected | Get-ChildItem -ErrorAction SilentlyContinue | Remove-Item -Force -Confirm:$false Start-Process ` -FilePath "$($CompilationToolPath)\rc.exe" ` -ArgumentList "$($fileName).rc" ` -WorkingDirectory $TempPath ` -Wait ` -WindowStyle Hidden Write-PSFMessage -Level Debug -Message "Validating generated '$($fileName).res' file" foreach ($tempFile in $tempFilesExpected) { if (Test-Path -Path $tempFile -NewerThan (Get-Date).AddSeconds(-5)) { $tempFilesExisting += Get-ChildItem $tempFile -ErrorAction Stop } else { Stop-PSFFunction -Message "Expected temp file '$($tempFile)' is present, but has a too old timestamp. Something went wrong. Aborting process" -EnableException $true } } Write-PSFMessage -Level Debug -Message "Res file generated: $([string]::Join(", ", $tempFilesExpected)) " #endregion generates "**.res" file from xml manifest #region final compilation of the dll file Write-PSFMessage -Level Debug -Message "Finally compiling '$fileName).dll' file from generated meta files" Start-Process ` -FilePath "$($WindowsCSCPath)\csc.exe" ` -ArgumentList "/win32res:$($TempPath)\$($fileName).res /unsafe /target:library /out:$($TempPath)\$($fileName).dll $($TempPath)\$($fileName).cs" ` -WorkingDirectory $TempPath ` -Wait ` -WindowStyle Hidden Write-PSFMessage -Level Debug -Message "Validating generated '$($fileName).dll' file" foreach ($FinalFile in $finalFilesExpected) { if (Test-Path -Path $FinalFile -NewerThan (Get-Date).AddSeconds(-15)) { $finalFilesExisting += Get-ChildItem $FinalFile -ErrorAction Stop } else { Stop-PSFFunction -Message "Expected temp file '$($FinalFile)' is present, but has a too old timestamp. Something went wrong. Aborting process" -EnableException $true } } Write-PSFMessage -Level Debug -Message "DLL file generated: $($TempPath)\$($fileName).dll" if ($pscmdlet.ShouldProcess("'$($fileNameManifest)' and '$($fileNameDLL)' in '$($DestinationPath)'", "Create")) { Write-PSFMessage -Level Verbose -Message "Writing final $($finalFilesExisting.Count) files to '$($DestinationPath)'" $finalFilesExisting | Copy-Item -Destination $DestinationPath -Force -ErrorAction Stop } #endregion final compilation of the dll file Write-PSFMessage -Level Verbose -Message "Finished process group '$($baseName)'" #endregion Compile the manifest to DLL } #region Cleanup Write-PSFMessage -Level Verbose -Message "Cleaning up temporary path '$($TempPath)'" Remove-Item -Path $TempPath -Force -Recurse -ErrorAction SilentlyContinue #endregion Cleanup } } function Open-WELCExcelTemplate { <# .Synopsis Open-WELCExcelTemplate .DESCRIPTION Open Excel template file for managing custom EventLog channel definition For obvious reason, Excel or equivalent tools needs to be present on the machine .EXAMPLE PS C:\> Open-WELCExcelTemplate Open a new Excel file from a template file within the module WindowsEventLogCustomization .NOTES Author: Andreas Bellstedt .LINK https://github.com/AndiBellstedt/WinEventLogCustomization #> [CmdLetBinding( SupportsShouldProcess = $false, ConfirmImpact = 'Low' )] param( ) begin { } process { } end { $path = "$($ModuleRoot)\bin\WinEventLogCustomization.xltx" $pathExtension = $path.Split(".")[-1] $null = New-PSDrive -PSProvider registry -Root HKEY_CLASSES_ROOT -Name HKCR Write-PSFMessage -Level Debug -Message "Looking for application to open '$($pathExtension)' files" # parse registry for file extension $registryFileLinkInfo = Get-Item "HKCR:\.$($pathExtension)" -ErrorAction SilentlyContinue if ($registryFileLinkInfo) { # parse registry for linked appliaction info to open/ create new file from template $registryAppLinkInfo = Get-Item "HKCR:\$($registryFileLinkInfo.GetValue(''))\shell\new\command" -ErrorAction SilentlyContinue if (-not $registryAppLinkInfo) { Write-PSFMessage -Level Debug -Message "No 'create new' shell info found. Try to query 'shell open' info" $registryAppLinkInfo = Get-Item "HKCR:\$($registryFileLinkInfo.GetValue(''))\shell\Open\command" -ErrorAction SilentlyContinue } } if ($registryAppLinkInfo) { Write-PSFMessage -Level Debug -Message "Found application info '$($registryFileLinkInfo.GetValue(''))'. Parsing shell command '$($registryAppLinkInfo.GetValue(''))'" $invokeCmd = $registryAppLinkInfo.GetValue('') -replace "%1", $path $invokeCmd = ($invokeCmd -split '"') | Where-Object { $_ -and $_ -notlike " " } | ForEach-Object { $_.trim() } } if ($invokeCmd.Count -ge 2) { Write-PSFMessage -Level Debug -Message "Found application link '$($registryFileLinkInfo.GetValue(''))' in registry to open template file. Going to invoke '$($invokeCmd[0])' $($invokeCmd[-1])" if (Test-Path -Path $path) { Write-PSFMessage -Level Verbose -Message "Opening '$($path)'" Start-Process -FilePath $invokeCmd[0] -ArgumentList $invokeCmd[1 .. ($invokeCmd.count - 1)] } else { Write-PSFMessage -Level Error -Message "Missing template file in module. Unable to find '$($path)'" } } else { if ($registryFileLinkInfo) { Write-PSFMessage -Level Debug -Message "Unable parse shell command to open template file from registry, but file extension info is present in registry. Try to blindly invoke the template file item as a fallback" Invoke-Item -Path $path } else { Write-PSFMessage -Level Error -Message "Unable to open template file, due to no application for opening '$($pathExtension)-files' seems to be installed" } } } } function Register-WELCEventChannelManifest { <# .SYNOPSIS Register-WELCEventChannelManifest .DESCRIPTION Register a compiled DLL and the manifest file to windows EventLog sytem The content of the registered manifest appears in EventLog reader unter Application and Services Logs .PARAMETER Path The path to the manifest (and the dll) file .PARAMETER ComputerName The computer where to register the manifest file .PARAMETER Session PowerShell Session object where to register the manifest file .PARAMETER DestinationPath The path where to store the manifest and DLL file By default, this is the same as "Path", as long, as you do not specify something else. If you use remoting to register the manifest on a remote computer the files will be copied over locally into DestinationPath on the remote computer .PARAMETER Credential The credentials to use on remote calls .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .NOTES Author: Andreas Bellstedt .LINK https://github.com/AndiBellstedt/WinEventLogCustomization .EXAMPLE PS C:\> Register-WELCEventChannelManifest -Path C:\CustomDLLPath\MyChannel.man Register the manfifest-file to Windows EventLog System, so it appears in Application and Services Logs. Next to the MyChannel.man file, there has to be a MyChannel.dll. The manifest and DLL file will be registered from the path C:\CustomDLLPath and has to remain there. .EXAMPLE PS C:\> Register-WELCEventChannelManifest -Path C:\CustomDLLPath\MyChannel.man -DestinationPath $env:WinDir\System32 Register the manfifest-file to Windows EventLog System, so it appears in Application and Services Logs. Next to the MyChannel.man file, there has to be a MyChannel.dll. The manifest and DLL file will be copied to the system32 directory of the current windows installation. From there it is registered and has to remain in that folder. .EXAMPLE PS C:\> Register-WELCEventChannelManifest -Path C:\CustomDLLPath\MyChannel.man -ComputerName SRV01 Register the manfifest-file to Windows EventLog System on the remote computer "SRV01". The manifest and DLL file will be registered from the the local path "C:\CustomDLLPath" on "SRV01" and has to remain there. .EXAMPLE PS C:\> Register-WELCEventChannelManifest -Path C:\CustomDLLPath\MyChannel.man -Sesion $PSSession Register the manfifest-file to Windows EventLog System on all connections within the $PSSession variable Assuming $PSSession variable is created something like this: $PSSession = New-PSSession -ComputerName SRV01 #> [CmdletBinding( SupportsShouldProcess = $true, PositionalBinding = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'ComputerName' )] param ( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [Alias("File", "FileName", "FullName")] [String[]] $Path, [Parameter( ParameterSetName = "ComputerName", ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 1 )] [Alias("Host", "Hostname", "Computer", "DNSHostName")] [PSFComputer[]] $ComputerName = $env:COMPUTERNAME, [Parameter( ParameterSetName = "Session", Position = 1 )] [System.Management.Automation.Runspaces.PSSession[]] $Session, [Parameter(ParameterSetName = "ComputerName")] [PSCredential] $Credential, [String] $DestinationPath ) begin { # If session parameter is used -> transfer it to ComputerName, # The class "PSFComputer" from PSFramework can handle it. This simplifies the handling in the further process block if ($Session) { $ComputerName = $Session.ComputerName } $DestinationPath = $DestinationPath.TrimEnd("\") $pathBound = Test-PSFParameterBinding -ParameterName Path $computerBound = Test-PSFParameterBinding -ParameterName ComputerName } process { #region parameterset workarround Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($PsCmdlet.ParameterSetName)" # Workarround parameter binding behaviour of powershell in combination with ComputerName Piping if (-not ($pathBound -or $computerBound) -and $ComputerName.InputObject -and $PSCmdlet.ParameterSetName -ne "Session") { if ($ComputerName.InputObject -is [string]) { $ComputerName = $env:ComputerName } else { $Path = "" } } #endregion parameterset workarround #region Processing Events foreach ($pathItem in $Path) { # File and folder validation if (Test-Path -Path $pathItem -PathType Leaf) { Write-PSFMessage -Level Verbose -Message "Found file '$($pathItem)' as a valid file in path" -Target $env:COMPUTERNAME $files = $pathItem | Resolve-Path | Get-ChildItem | Select-Object -ExpandProperty FullName } elseif (Test-Path -Path $pathItem -PathType Container) { Write-PSFMessage -Level Verbose -Message "Getting files in path '$($pathItem)'" -Target $env:COMPUTERNAME $files = Get-ChildItem -Path $pathItem -File -Filter "*.man" | Select-Object -ExpandProperty FullName Write-PSFMessage -Level Verbose -Message "Found $($files.count) file$(if($files.count -gt 1){"s"}) in path" -Target $env:COMPUTERNAME if (-not $files) { Write-PSFMessage -Level Warning -Message "No manifest files found in path '$($pathItem)'" -Target $env:COMPUTERNAME } } elseif (-not (Test-Path -Path $pathItem -PathType Any -IsValid)) { Write-PSFMessage -Level Error -Message "'$pathItem' is not a valid path or file." -Target $env:COMPUTERNAME continue } else { Write-PSFMessage -Level Error -Message "unable to open '$($pathItem)'" -Target $env:COMPUTERNAME continue } foreach ($file in $files) { if (-not $DestinationPath) { $DestinationPath = Split-Path -Path $file } # Check for dll paths in manifest / prepare dll paths in manifest for different destination path if ( (-not (Test-WELCEventChannelManifest -Path $file -OnlyDLLPath)) -or ((split-path $file) -notlike $DestinationPath) ) { [String]$tempPath = "$($env:TEMP)\WELC_$([guid]::NewGuid().guid)" if (Test-Path -Path $tempPath -IsValid) { if (-not (Test-Path -Path $tempPath -PathType Container)) { New-Item -Path $tempPath -ItemType Directory -Force | Out-Null $tempPath = Resolve-Path $tempPath -ErrorAction Stop | Select-Object -ExpandProperty Path } } Write-PSFMessage -Level Verbose -Message "Prepare '$($file)' for destination '$($DestinationPath)'" $tempFile = Move-WELCEventChannelManifest -Path $file -DestinationPath $tempPath -CopyMode -PassThru -ErrorAction Stop | Select-Object -ExpandProperty FullName $file = Move-WELCEventChannelManifest -Path $tempFile -DestinationPath $DestinationPath -Prepare -PassThru | Select-Object -ExpandProperty FullName } Write-PSFMessage -Level Verbose -Message "Opening XML manifest file '$($file)' to gather DLL information" $xmlfile = New-Object XML $xmlfile.Load($file) $dllFiles = @() Write-PSFMessage -Level Debug -Message "Gather path of resourceFileName DLL" $dllFileList = $xmlfile.instrumentationManifest.instrumentation.events.provider.resourceFileName foreach ($dllFile in $dllFileList) { if ((Test-Path -Path $dllFile -PathType Leaf) -and ((Split-Path -Path $dllFile) -notlike $DestinationPath)) { $dllFiles += $dllFile } else { $dllFile = "$(split-path $file)\$(Split-Path -Path $dllFile -Leaf)" if (Test-Path -Path $dllFile -PathType Leaf) { $dllFiles += $dllFile } else { Stop-PSFFunction -Message "Unexpected behavior while locating ressource dll file" } } } Write-PSFMessage -Level Debug -Message "Gather path of messageFileName DLL" $dllFileList = $xmlfile.instrumentationManifest.instrumentation.events.provider.messageFileName foreach ($dllFile in $dllFileList) { if ((Test-Path -Path $dllFile -PathType Leaf) -and ((Split-Path -Path $dllFile) -notlike $DestinationPath)) { $dllFiles += $dllFile } else { $dllFile = "$(split-path $file)\$(Split-Path -Path $dllFile -Leaf)" if (Test-Path -Path $dllFile -PathType Leaf) { $dllFiles += $dllFile } else { Stop-PSFFunction -Message "Unexpected behavior while locating message dll file" } } } Write-PSFMessage -Level Debug -Message "Gather path of parameterFileName DLL" $dllFileList = $xmlfile.instrumentationManifest.instrumentation.events.provider.parameterFileName foreach ($dllFile in $dllFileList) { if ((Test-Path -Path $dllFile -PathType Leaf) -and ((Split-Path -Path $dllFile) -notlike $DestinationPath)) { $dllFiles += $dllFile } else { $dllFile = "$(split-path $file)\$(Split-Path -Path $dllFile -Leaf)" if (Test-Path -Path $dllFile -PathType Leaf) { $dllFiles += $dllFile } else { Stop-PSFFunction -Message "Unexpected behavior while locating parameter dll file" } } } $dllFiles = $dllFiles | Sort-Object -Unique Write-PSFMessage -Level Verbose -Message "Found $($dllFiles.count) dll: $([string]::Join(", ", $dllFiles))" # Process computers foreach ($computer in $ComputerName) { Write-PSFMessage -Level Verbose -Message "Processing file '$($file)' on computer '$($computer)'" # When remoting is used, transfer files first if (($PSCmdlet.ParameterSetName -eq "Session") -or (-not $computer.IsLocalhost)) { Write-PSFMessage -Level Verbose -Message "Going to transfer file into destination '$($DestinationPath)' on remote computer" if ($pscmdlet.ShouldProcess("Manifest '$($file)' and dll to computer '$($computer)'", "Transfer")) { # Create PS remoting session if ($PSCmdlet.ParameterSetName -ne "Session") { $paramSession = @{ "ComputerName" = $computer.ToString() "ErrorAction" = "Stop" } if ($Credential) { $paramSession.Add("Credential", $Credential) } try { $Session = New-PSSession @paramSession } catch { Write-PSFMessage -Level Error -Message "Error creating remoting session to computer '$($computer)'" -Target $computer -ErrorRecord $_ break } } # Transfer files Copy-Item -ToSession $Session -Destination $DestinationPath -Force -Path $file Copy-Item -ToSession $Session -Destination $DestinationPath -Force -Path $dllFiles } } elseif ((split-path $file) -notlike $DestinationPath) { Write-PSFMessage -Level Verbose -Message "Going to copy file into destination '$($DestinationPath)'" Copy-Item -Destination $DestinationPath -Force -Path $file Copy-Item -Destination $DestinationPath -Force -Path $dllFiles } # Register manifest if ($pscmdlet.ShouldProcess("Manifest '$($file)' on computer '$($computer)'", "Register")) { $destFileName = "$($DestinationPath)\$(split-path $file -Leaf)" $paramInvokeCmd = [ordered]@{ "ComputerName" = $computer.ToString() "ErrorAction" = "Stop" ErrorVariable = "ErrorReturn" "ArgumentList" = $destFileName } if ($PSCmdlet.ParameterSetName -eq "Session") { $paramInvokeCmd['ComputerName'] = $Session } if ($Credential) { $paramInvokeCmd.Add("Credential", $Credential) } Write-PSFMessage -Level Verbose -Message "Registering manifest '$($destFileName)' on computer '$($computer)'" -Target $computer try { $null = Invoke-PSFCommand @paramInvokeCmd -ScriptBlock { try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch { Write-Information -MessageData "Exception while setting UTF8 OutputEncoding. Continue script." } $output = . "$($env:windir)\system32\wevtutil.exe" "install-manifest" "$($args[0])" *>&1 $output = $output | Where-Object { $_.InvocationInfo.MyCommand.Name -like 'wevtutil.exe' } *>&1 if ($output) { Write-Error -Message "$([string]::Join(" ", $output.Exception.Message.Replace("`r`n"," ")))" -ErrorAction Stop } } if ($ErrorReturn) { Write-Error "Error registering manifest" -ErrorAction Stop } } catch { Stop-PSFFunction -Message "Unable to register manifest '$($destFileName)' on computer '$($computer)'" -Target $computer -ErrorRecord $_ } } } if ($tempPath) { Write-PSFMessage -Level Debug -Message "Cleaning up temporary directory '$($tempPath)'" Remove-Item -Path $tempPath -Recurse -Force -Confirm:$false -WhatIf:$false -Verbose:$false -Debug:$false -ErrorAction:Ignore } } } } end { } } function Set-WELCEventChannel { <# .SYNOPSIS Set-WELCEventChannel .DESCRIPTION Set various properties on a EventChannel .PARAMETER ChannelConfig InputObject (WELC.ChannelConfig) from Excel Template file Can be created by Import-WELCChannelDefinition -Path ".\MyTemplate.xls" -OutputChannelConfig .PARAMETER EventChannel InputObject from Get-WELCEventChannel Available Alias on the parameter: EventLogChannel .PARAMETER ChannelFullName The name of the channel to be set .PARAMETER ComputerName The computer where to set the configuration .PARAMETER Enabled The status of the logfile. By default the logfiles are enabled after execution .PARAMETER LogMode Specifies how events in the EventChannel are treated, if the EventChannel reaches the maximum. Possilibilites: "AutoBackup" = File from EventChannel will be renamed and a newly file will be created "Circular" = Oldest event will be overwritten "Retain" = Newly events will be refused .PARAMETER LogFilePath The path in the filesystem for the EventChannel EVTX file This can be a full qualified filename - if only ONE EventChannel is to be set/ piped in. Effectivly this means a rename of the file for the EventChannel. If multiple configurations should be set/ piped in, the value on this parameter should be a FOLDER, not a file! .PARAMETER CompressLogFolder Specifies if the folder with the log files get compressed .PARAMETER MaxEventLogSize The maximum size in bytes for the EventChannel .PARAMETER AllowFileAccessForLocalService The WellKnownPrincipal 'Local Service' will add in the NTFS ACL to gain access to the logfile If the channel is planned to be used in "Windows Event Forwarding" this should probably set to true .PARAMETER EventChannelSDDL SDDL string to set access on the eventlog within the WinodwsEventLog system itself This access object controls who can view events in the EventLog for this channel So it's not on the filesystem, it's in MMC, WMI or PowerShell .PARAMETER PassThru The moved files will be parsed to the pipeline for further processing. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .NOTES Author: Andreas Bellstedt This a is quite far modified version from Project Sauron fork. Name: Prep-EventChannels.ps1 Version: 1.1 Author: Russell Tomkins - Microsoft Premier Field Engineer Blog: https://aka.ms/russellt Preparation of event channels to receive event collection subscriptions from an input CSV Source: https://www.github.com/russelltomkins/ProjectSauron Refer to this blog series for more details http://blogs.technet.microsoft.com/russellt/2017/03/23/project-sauron-part-1 .LINK https://github.com/AndiBellstedt/WinEventLogCustomization .EXAMPLE PS C:\> Set-WELCEventChannel -ChannelFullName "App1/MyLog" -LogMode Circular -LogFilePath "C:\EventLogs\App1-MyLog.evtx" Set the 'MyLog' EventLog in the EventFolder 'App1' to circular logging and the path of the logfile to 'C:\EventLogs\App1-MyLog.evtx' .EXAMPLE PS C:\> $channels | Set-WELCEventChannel -Enabled $true -MaxEventLogSize 1GB Enables the EventChannels from the $channels variable and set maximum size to 1GB. Assuming the $channels variable is filled with something like $channels = Get-WELCEventChannel -ChannelFullName "App1/MyLog", "App2/MyLog" .EXAMPLE PS C:\> $ChannelConfig | Set-WELCEventChannel -Enabled $true -CompressLogFolder $true -AllowFileAccessForLocalService $true Enables the EventChannels from the $ChannelConfig variable and set all the properties within the $ChannelConfig variable. Additionally, the logfile/logfolder for the EventLogs will be compressed (except if it is a folder in Windows\System32), and the SID for "Local Service" gain Read/Write-Access. Assuming the $channels variable is filled with something like $ChannelConfig = Import-WELCChannelDefinition -Path C:\EventLogs\WinEventLogCustomization.xlsx -OutputChannelConfig Excel template file can be created/opened with Open-WELCExcelTemplate #> [CmdletBinding( DefaultParameterSetName = "ChannelName", SupportsShouldProcess = $true, PositionalBinding = $true, ConfirmImpact = 'Medium' )] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", '', Justification = "Intentional, Pester not covering the usage correct")] Param( [Parameter( ParameterSetName = "TemplateChannelConfig", Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [WELC.ChannelConfig[]] $ChannelConfig, [Parameter( ParameterSetName = "EventChannel", Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0 )] [Alias("EventLogChannel")] [WELC.EventLogChannel[]] $EventChannel, [Parameter( ParameterSetName = "ChannelName", Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0 )] [Alias("ChannelName")] [String[]] $ChannelFullName, [Parameter( ParameterSetName = "TemplateChannelConfig", ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 1 )] [Parameter( ParameterSetName = "ChannelName", ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 1 )] [Alias("Host", "Hostname", "Computer", "DNSHostName")] [PSFComputer[]] $ComputerName = $env:COMPUTERNAME, [Parameter(ParameterSetName = "EventChannel")] [Parameter(ParameterSetName = "ChannelName")] [bool] $Enabled, [Parameter(ParameterSetName = "EventChannel")] [Parameter(ParameterSetName = "ChannelName")] [int] $MaxEventLogSize, [Parameter(ParameterSetName = "EventChannel")] [Parameter(ParameterSetName = "ChannelName")] [ValidateSet("AutoBackup", "Circular", "Retain")] [string] $LogMode, [Parameter(ParameterSetName = "EventChannel")] [Parameter(ParameterSetName = "ChannelName")] [String] $LogFilePath, [Parameter(ParameterSetName = "EventChannel")] [Parameter(ParameterSetName = "ChannelName")] [Alias("Compress")] [bool] $CompressLogFolder, [bool] $AllowFileAccessForLocalService, [String] $EventChannelSDDL, [switch] $PassThru ) Begin { $channelFullNameBound = Test-PSFParameterBinding -ParameterName ChannelFullName $computerBound = Test-PSFParameterBinding -ParameterName ComputerName^ $configList = New-Object System.Collections.ArrayList # validation on parameter LogFilePath if ($LogFilePath) { $LogFilePath = $LogFilePath.TrimEnd("\") if ($LogFilePath -like '%SystemRoot%*') { $LogFilePath = $LogFilePath.Replace('%SystemRoot%', $env:SystemRoot) } if ($LogFilePath.EndsWith(".evtx")) { $logFileFolder = Split-Path -Path $LogFilePath $logFileFullName = $LogFilePath } else { $logFileFolder = $LogFilePath $logFileFullName = "ToBeCalculated" } } # if compression is set and windows-folder is specified as LogFilePath -> abort, to avoid 'unhealthy' system modification if ($LogFilePath -like "$($env:SystemRoot)\System32\*" -and $CompressLogFolder -eq $true) { Stop-PSFFunction -Message "Hardcoded exception, not going to set compression within windows-System32-folder. Aborting function" -EnableException $true break } } Process { #region parameterset workarround Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($PsCmdlet.ParameterSetName)" #ToDo: Check if this behaves right. What is if ComputerName "foo" is piped in and Channelfullname is specified, and vice versa # Workarround parameter binding behaviour of powershell in combination with ComputerName Piping if (-not ($channelFullNameBound -or $computerBound) -and $ComputerName.InputObject) { if ($ComputerName.InputObject -is [string]) { $ComputerName = $env:ComputerName } else { $ChannelFullName = "" } } #endregion parameterset workarround switch ($pscmdlet.ParameterSetName) { "TemplateChannelConfig" { Write-PSFMessage -Level Verbose -Message "Gathering $(([array]$ChannelConfig).count) channel configurations" foreach ($channelConfigItem in $ChannelConfig) { foreach ($computer in $ComputerName) { $eventChannel = $null $eventChannel = Get-WELCEventChannel -ChannelFullName $channelConfigItem.ChannelName -ComputerName $computer -ErrorAction SilentlyContinue if ($eventChannel) { foreach ($eventChannelItem in $eventChannel) { Write-PSFMessage -Level Debug -Message "Collecting config object for '$($eventChannelItem.Name)' on '$($computer)'" $null = $configList.Add( [PSCustomObject]@{ EventChannel = $eventChannelItem Enabled = $channelConfigItem.Enabled MaxEventLogSize = $channelConfigItem.MaxEventLogSize LogMode = $channelConfigItem.LogMode LogFileFullName = $channelConfigItem.LogFullName LogFilePath = (Split-Path -Path $channelConfigItem.LogFullName) CompressLogFolder = $CompressLogFolder AllowFileAccessForLocalService = $AllowFileAccessForLocalService EventChannelSDDL = $EventChannelSDDL } ) } } else { Write-PSFMessage -Level Warning -Message "Skipping '$($channelConfigItem.ChannelName)' on '$($computer)'" continue } } } } "EventChannel" { foreach ($eventChannelItem in $EventChannel) { Write-PSFMessage -Level Debug -Message "Collecting config object for '$($eventChannelItem.ChannelFullName)' on '$($eventChannelItem.ComputerName)'" $null = $configList.Add( [PSCustomObject]@{ EventChannel = $eventChannelItem Enabled = ( if (Test-PSFParameterBinding -ParameterName Enabled) { $Enabled } ) MaxEventLogSize = ( if (Test-PSFParameterBinding -ParameterName MaxEventLogSize) { $MaxEventLogSize } ) LogMode = ( if (Test-PSFParameterBinding -ParameterName LogMode) { $LogMode } ) LogFileFullName = ( if ($logFileFullName -like "ToBeCalculated") { "$($logFileFolder)\$($eventChannelItem.LogFile)" } elseif (Test-PSFParameterBinding -ParameterName LogFilePath) { $logFileFullName } ) LogFilePath = ( if (Test-PSFParameterBinding -ParameterName LogFilePath) { $logFileFolder } ) CompressLogFolder = ( if (Test-PSFParameterBinding -ParameterName CompressLogFolder) { $CompressLogFolder } ) AllowFileAccessForLocalService = ( if (Test-PSFParameterBinding -ParameterName AllowFileAccessForLocalService) { $AllowFileAccessForLocalService } ) EventChannelSDDL = ( if (Test-PSFParameterBinding -ParameterName EventChannelSDDL) { $EventChannelSDDL } ) } ) } } "ChannelName" { foreach ($channelNameItem in $ChannelFullName) { foreach ($computer in $ComputerName) { $eventChannel = $null $eventChannel = Get-WELCEventChannel -ChannelFullName $channelNameItem -ComputerName $computer -ErrorAction SilentlyContinue if ($eventChannel) { foreach ($eventChannelItem in $eventChannel) { Write-PSFMessage -Level Debug -Message "Collecting config object for '$($eventChannelItem.Name)' on '$($computer)'" $null = $configList.Add( [PSCustomObject]@{ EventChannel = $eventChannelItem Enabled = ( if (Test-PSFParameterBinding -ParameterName Enabled) { $Enabled } ) MaxEventLogSize = ( if (Test-PSFParameterBinding -ParameterName MaxEventLogSize) { $MaxEventLogSize } ) LogMode = ( if (Test-PSFParameterBinding -ParameterName LogMode) { $LogMode } ) LogFileFullName = ( if ($logFileFullName -like "ToBeCalculated") { "$($logFileFolder)\$($eventChannelItem.LogFile)" } elseif (Test-PSFParameterBinding -ParameterName LogFilePath) { $logFileFullName } ) LogFilePath = ( if (Test-PSFParameterBinding -ParameterName LogFilePath) { $logFileFolder } ) CompressLogFolder = ( if (Test-PSFParameterBinding -ParameterName CompressLogFolder) { $CompressLogFolder } ) AllowFileAccessForLocalService = ( if (Test-PSFParameterBinding -ParameterName AllowFileAccessForLocalService) { $AllowFileAccessForLocalService } ) EventChannelSDDL = ( if (Test-PSFParameterBinding -ParameterName EventChannelSDDL) { $EventChannelSDDL } ) } ) } } else { Write-PSFMessage -Level Warning -Message "Skipping '$($channelNameItem)' on '$($computer)'" continue } } } } Default { Stop-PSFFunction -Message "Unhandeled ParameterSetName. Developers mistake." -EnableException $true throw } } } End { # invalid configuration attempt - Parameter 'LogFilePath' specified as file and multiple channels should be configured if ($pscmdlet.ParameterSetName -notlike "TemplateChannelConfig" -and $LogFilePath -and $logFileFullName -notlike "ToBeCalculated" -and $configList.count -gt 1) { Stop-PSFFunction -Message "Parameter 'LogFilePath' was specified as a file, but more than one EventChannels where specified/piped. This leads to unvalid configuration. Each EventChannel has to have it's own LogFile. Please specify a folder on parameter 'LogFilePath'. Aborting..." -EnableException $true throw } Write-PSFMessage -Level Verbose -Message "Working trough list of $($configList.Count) collected EventChannel$(if($configList.Count -gt 1){"s"}) to configure" foreach ($configItem in $configList) { Write-PSFMessage -Level Verbose -Message "Processing '$($configItem.EventChannel.Name)' on '$($configItem.EventChannel.PSComputerName)'" # Get current folder for EventLogFile $eventLogFolderCurrent = Invoke-PSFCommand -ComputerName $configItem.EventChannel.PSComputerName -ArgumentList $configItem.EventChannel.LogFolder -ScriptBlock { $_query = "select * from CIM_Directory where name = `"$($args[0].Replace('\','\\'))`"" Get-CimInstance -Query $_query -ErrorAction SilentlyContinue -Verbose:$false -Debug:$false } # Check if FilePath for EventChannel should be modified if ($configItem.LogFilePath -and ($eventLogFolderCurrent.Name -notlike $configItem.LogFilePath)) { Write-PSFMessage -Level Verbose -Message "Set new path '$($configItem.LogFilePath)' for EventChannel" # First, check if destination folder is already present Write-PSFMessage -Level Debug -Message "Test new path: $($configItem.LogFilePath)" $destinationFolder = Invoke-PSFCommand -ComputerName $configItem.EventChannel.PSComputerName -ArgumentList $configItem.LogFilePath -ErrorVariable invokeErrors -ScriptBlock { $_query = "select * from CIM_Directory where name = `"$($args[0].Replace('\','\\'))`"" $_result = Get-CimInstance -Query $_query -ErrorAction Ignore -Verbose:$false -Debug:$false # if folder is not present, try to query root folder, to check if drive is valid if (-not $_result) { $null = Get-Item -Path "$($args[0].split("\")[0])\" -ErrorAction SilentlyContinue } else { $_result } } # if error occured = drive is not present/valid if ($invokeErrors.Exception) { Write-PSFMessage -Level Error -Message "Invalid path '$($configItem.LogFilePath)' on system '$($configItem.EventChannel.PSComputerName)'! Possibly inaccessable drive/volume" -EnableException $true -Exception $invokeErrors.Exception -ErrorRecord $invokeErrors -Target $configItem.EventChannel.PSComputerName continue } Remove-Variable -Name invokeErrors -Force -Confirm:$false -WhatIf:$false -Verbose:$false -Debug:$false -ErrorAction Ignore # if folder is not available, create it If (-not $destinationFolder) { if ($pscmdlet.ShouldProcess("EventChannel '$($configItem.EventChannel.Name)' on computer '$($configItem.EventChannel.PSComputerName)'", "Create folder '$($configItem.LogFilePath)'")) { Write-PSFMessage -Level Verbose -Message "Create folder '$($configItem.LogFilePath)' for EventChannel" try { $destinationFolder = Invoke-PSFCommand -ComputerName $configItem.EventChannel.PSComputerName -ArgumentList $configItem.LogFilePath -ErrorAction Stop -ErrorVariable invokeErrors -ScriptBlock { $_folder = New-Item -Type Directory -Path $args[0] -Force -ErrorAction Stop $_query = "select * from CIM_Directory where name = `"$($_folder.FullName.Replace('\','\\'))`"" Get-CimInstance -Query $_query -ErrorAction SilentlyContinue -Verbose:$false -Debug:$false } } catch { Write-PSFMessage -Level Error -Message "Unable to create folder '$($configItem.LogFilePath)' on '$($configItem.EventChannel.PSComputerName)'" -EnableException $true -Exception $_.Exception -ErrorRecord $_ -Target $configItem.EventChannel.PSComputerName continue } } } # Set new folder to EventChannel if ($pscmdlet.ShouldProcess("EventChannel '$($configItem.EventChannel.Name)' on computer '$($configItem.EventChannel.PSComputerName)'", "Set new destination '$($configItem.LogFileFullName)'")) { Write-PSFMessage -Level Verbose -Message "Set new destination '$($configItem.LogFileFullName)' for EventChannel '$($configItem.EventChannel.Name)' on computer '$($configItem.EventChannel.PSComputerName)'" $invokeParam = @{ "ComputerName" = $configItem.EventChannel.PSComputerName "ArgumentList" = ($configItem.EventChannel.Name , $configItem.LogFileFullName) "ErrorAction" = "Stop" "ErrorVariable" = "invokeErrors" } try { Invoke-PSFCommand @invokeParam -ScriptBlock { $_eventChannelName = $args[0] $_logFileFullName = $args[1] $_channel = Get-WinEvent -ListLog $_eventChannelName -Force -ErrorAction Stop $error.Clear() # backup current status $_currentIsEnabled = $_channel.IsEnabled # Disable to change settings $_channel.IsEnabled = $false $_channel.SaveChanges() # Set path on channel $_channel.LogFilePath = $_logFileFullName $_channel.IsEnabled = $_currentIsEnabled $_channel.SaveChanges() if ($error.Exception) { Write-Error "" -ErrorAction Stop } } } catch { Write-PSFMessage -Level Error -Message "Unable to set new destination '$($configItem.LogFileFullName)' on '$($configItem.EventChannel.PSComputerName)'" -EnableException $true -Exception $_.Exception -ErrorRecord $_ -Target $configItem.EventChannel.PSComputerName } } } else { $destinationFolder = $eventLogFolderCurrent } Remove-Variable -Name invokeErrors -Force -Confirm:$false -WhatIf:$false -Verbose:$false -Debug:$false -ErrorAction Ignore # Add an ACE to allow LOCAL SERVICE to modify the folder if ($configItem.AllowFileAccessForLocalService) { if ($pscmdlet.ShouldProcess("EventChannel '$($configItem.EventChannel.Name)' on computer '$($configItem.EventChannel.PSComputerName)'", "Add ACE for 'localSystem' on folder '$($configItem.LogFilePath)'")) { Write-PSFMessage -Level Verbose -Message "Set ntfs permission for 'local service' to folder '$($destinationFolder.name)' on '$($configItem.EventChannel.PSComputerName)'" $invokeParam = @{ "ComputerName" = $configItem.EventChannel.PSComputerName "ArgumentList" = ($destinationFolder.name) "ErrorAction" = "Stop" "ErrorVariable" = "invokeErrors" } try { Invoke-PSFCommand @invokeParam -ScriptBlock { $_destinationFolderName = $args[0] $ace = New-Object System.Security.AccessControl.FileSystemAccessRule( [System.Security.Principal.SecurityIdentifier]::new("S-1-5-19").Translate([System.Security.Principal.NTAccount]).Value, 'Modify', 'ContainerInherit,ObjectInherit', 'None', 'Allow' ) $logPathACL = $_destinationFolderName | Get-Item -ErrorAction Stop | Get-ACL -ErrorAction Stop if (-not ($logPathACL.Access | Where-Object { $_.IdentityReference -like $ace.IdentityReference -and $_.FileSystemRights -like $ace.FileSystemRights -and $_.AccessControlType -like $ace.AccessControlType })) { $logPathACL.AddAccessRule($ace) $logPathACL | Set-ACL -ErrorAction Stop } } } catch { Write-PSFMessage -Level Error -Message "Unable to set ntfs permission for 'local service' to folder '$($destinationFolder.name)' on '$($configItem.EventChannel.PSComputerName)'. Message: $($_.Exception.Message)" -EnableException $true -Exception $_.Exception -ErrorRecord $_ -Target $configItem.EventChannel.PSComputerName continue } Remove-Variable -Name invokeErrors -Force -Confirm:$false -WhatIf:$false -Verbose:$false -Debug:$false -ErrorAction Ignore } } # Set compression on folder if ($configItem.CompressLogFolder) { if ($pscmdlet.ShouldProcess("EventChannel '$($configItem.EventChannel.Name)' on computer '$($configItem.EventChannel.PSComputerName)'", "Set compression '$($configItem.CompressLogFolder)' on folder '$($configItem.LogFilePath)'")) { Write-PSFMessage -Level Verbose -Message "Set compression '$($configItem.CompressLogFolder)' to folder '$($destinationFolder)' on '$($configItem.EventChannel.PSComputerName)'" $invokeParam = @{ "ComputerName" = $configItem.EventChannel.PSComputerName "ArgumentList" = ($destinationFolder, $configItem.CompressLogFolder) "ErrorAction" = "Stop" "ErrorVariable" = "invokeErrors" "WarningAction" = "SilentlyContinue" "WarningVariable" = "invokeWarnings" } try { Invoke-PSFCommand @invokeParam -ScriptBlock { $_destinationFolder = $args[0] $_Compression = $args[1] $_query = "select * from CIM_Directory where name = `"$($_destinationFolder.Name.Replace('\','\\'))`"" $_cimResult = $null $returnCodes = [ordered]@{ 0 = "Success" 2 = "Access denied" 8 = "Unspecified failure" 9 = "Invalid object" 10 = "Object already exists" 11 = "File system not NTFS" 12 = "Platform not Windows" 13 = "Drive not the same" 14 = "Directory not empty" 15 = "Sharing violation" 16 = "Invalid start file" 17 = "Privilege not held" 21 = "Invalid parameter" } if ($_Compression -eq $true -and $_destinationFolder.Compressed -eq $false) { $_cimResult = Invoke-CimMethod -Query $_query -MethodName Compress -ErrorAction Stop -Verbose:$false } elseif ($_Compression -eq $false -and $_destinationFolder.Compressed -eq $true) { $_cimResult = Invoke-CimMethod -Query $_query -MethodName Uncompress -ErrorAction Stop -Verbose:$false } else { Write-Warning "Noting to do on '$($_destinationFolder.Name)'" -WarningAction SilentlyContinue } if ($_cimResult) { if ($_cimResult.ReturnValue -notin (0, 15)) { Write-Error "Error '$($_cimResult.ReturnValue) ($($returnCodes[$_cimResult.ReturnValue]))' occured while set compression attribute '$($_Compression)'" -ErrorAction Stop } elseif ($_cimResult.ReturnValue -eq 15) { Write-Warning "Compression attribute set, but with sharing violation. Means, attribute could not be applied on all files in folder" -WarningAction SilentlyContinue } } } } catch { Write-PSFMessage -Level Error -Message "Unable to set compression to folder '$($destinationFolder.name)' on '$($configItem.EventChannel.PSComputerName)'. Message: $($_.Exception.Message)" -EnableException $true -Exception $_.Exception -ErrorRecord $_ -Target $configItem.EventChannel.PSComputerName } if ($invokeWarnings) { $invokeWarnings | ForEach-Object { Write-PSFMessage -Level Warning -Message $_ } } Remove-Variable -Name invokeErrors, invokeWarnings -Force -Confirm:$false -WhatIf:$false -Verbose:$false -Debug:$false -ErrorAction Ignore } } # Set all the other possible EventChannel configs, including "Enabled" as the last setting $valueToSet = $configItem.psobject.Properties | Where-Object Name -in ("Enabled", "MaxEventLogSize", "LogMode", "EventChannelSDDL") | Where-Object { $_.Value -is $_.TypeNameOfValue } if ($valueToSet) { $valueToSetText = [string]::Join(', ', ($valueToSet | ForEach-Object { "$($_.Name)=$($_.Value)" })) if ($pscmdlet.ShouldProcess("EventChannel '$($configItem.EventChannel.Name)' on computer '$($configItem.EventChannel.PSComputerName)'", "Set '$($valueToSetText)'")) { Write-PSFMessage -Level Verbose -Message "Set '$($valueToSetText)' to EventChannel '$($configItem.EventChannel.Name)' on '$($configItem.EventChannel.PSComputerName)'" $invokeParam = @{ "ComputerName" = $configItem.EventChannel.PSComputerName "ArgumentList" = ($configItem.EventChannel.Name, ($valueToSet | Select-Object Name, Value)) "ErrorAction" = "Stop" "ErrorVariable" = "invokeErrors" } try { Invoke-PSFCommand @invokeParam -ScriptBlock { $_eventChannelName = $args[0] $_valueToSet = $args[1] $_translateSettingToProperty = @{ "Enabled" = "IsEnabled" "MaxEventLogSize" = "MaximumSizeInBytes" "LogMode" = "LogMode" "EventChannelSDDL" = "SecurityDescriptor" } $_channel = Get-WinEvent -ListLog $_eventChannelName -Force -ErrorAction Stop $error.Clear() # backup current status $_currentIsEnabled = $_channel.IsEnabled # Disable to change settings $_channel.IsEnabled = $false $_channel.SaveChanges() # Set properties on channel foreach ($setting in ($_valueToSet | Where-Object name -NotLike "Enabled")) { $_channel.($_translateSettingToProperty[$setting.Name]) = $setting.Value } $_desiredChannelStatus = $_valueToSet | Where-Object name -Like "Enabled" if ($_desiredChannelStatus) { $_channel.IsEnabled = $_desiredChannelStatus.Value } else { $_channel.IsEnabled = $_currentIsEnabled } $_channel.SaveChanges() if ($error.Exception) { Write-Error "" -ErrorAction Stop } } } catch { Write-PSFMessage -Level Error -Message "Unable to set '$($valueToSetText)' to EventChannel '$($configItem.EventChannel.Name)' on '$($configItem.EventChannel.PSComputerName)'. Message: $($_.Exception.Message)" -EnableException $true -Exception $_.Exception -ErrorRecord $_ -Target $configItem.EventChannel.PSComputerName continue } Remove-Variable -Name invokeErrors -Force -Confirm:$false -WhatIf:$false -Verbose:$false -Debug:$false -ErrorAction Ignore } } # Output if specified if ($PassThru) { Get-WELCEventChannel -ChannelFullName $configItem.EventChannel.Name -ComputerName $configItem.EventChannel.PSComputerName -ErrorAction SilentlyContinue } } } } function Test-WELCEventChannelManifest { <# .SYNOPSIS Test-WELCEventChannelManifest .DESCRIPTION Test a man file for valid path with the compiled DLL file belonging to manifest file The manifest has to contain the fullname of the path where the dll file is stored, otherwise there will be errors when registering/usering it .PARAMETER Path The path to the manifest file .PARAMETER OnlyDLLPath Only verify path of DLL files in Manifest and skip validation of properties .PARAMETER Property Explicitly validate only the specified property .PARAMETER PassThru The moved files will be parsed to the pipeline for further processing. .NOTES Author: Andreas Bellstedt .LINK https://github.com/AndiBellstedt/WinEventLogCustomization .EXAMPLE PS C:\> Test-WELCEventChannelManifest -Path C:\CustomDLLPath\MyChannel.man Test the manifest. Show $true, if the manifest is a valid EventLogChannelManifest and the compiled DLL file ist in the expected directory Otherwise $false will be the result of the test. .EXAMPLE PS C:\> Test-WELCEventChannelManifest -Path C:\CustomDLLPath\MyChannel.man -OnlyDLLPath Same like first example, but skip structure and name checks. In fact, it only checks on reference path for DLL file. .EXAMPLE PS C:\> Test-WELCEventChannelManifest -Path C:\CustomDLLPath\MyChannel.man -PassThru Test the manifest. If the manifest is a valid EventLogChannelManifest and the compiled DLL file ist in the expected directory, the path (fullname) of the Manifest file will be the output #> [CmdletBinding( SupportsShouldProcess = $false, PositionalBinding = $true, ConfirmImpact = 'Low', DefaultParameterSetName = "General" )] [OutputType("System.Boolean")] param ( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [Alias("File", "FileName", "FullName")] [String[]] $Path, [Parameter(ParameterSetName = "General")] [switch] $OnlyDLLPath, [Parameter( Mandatory = $true, ParameterSetName = "ExplicitProperty" )] [ValidateSet("ProviderName", "ProviderGUID", "ProviderSymbol", "ResourceFileName", "MessageFileName", "ParameterFileName", "ChannelName", "ChannelSymbol", "Type", "Enabled")] [String[]] $Property, [switch] $PassThru ) begin { } process { foreach ($pathItem in $Path) { $isOK = $true # File and folder validity tests if ((Test-Path -Path $pathItem -PathType Leaf) -and ($pathItem.Split(".")[-1] -like "man")) { $file = $pathItem | Resolve-Path | Get-ChildItem | Select-Object -ExpandProperty FullName Write-PSFMessage -Level Verbose -Message "Found file '$($file )' as a valid file" } elseif (Test-Path -Path $pathItem -PathType Container) { Write-PSFMessage -Level Error -Message "'$pathItem' is a folder. Please specify a manifest file." continue } elseif (-not (Test-Path -Path $pathItem -PathType Any -IsValid)) { Write-PSFMessage -Level Error -Message "'$pathItem' is not a valid path or file." continue } else { Write-PSFMessage -Level Error -Message "Unable to open '$($pathItem)'" continue } # open XML file $xmlfile = New-Object XML $xmlfile.Load($file) if ( $xmlfile.instrumentationManifest.schemaLocation -eq "http://schemas.microsoft.com/win/2004/08/events eventman.xsd" -and $xmlfile.instrumentationManifest.xmlns -eq "http://schemas.microsoft.com/win/2004/08/events" -and $xmlfile.instrumentationManifest.win -eq "http://manifests.microsoft.com/win/2004/08/windows/events" -and $xmlfile.instrumentationManifest.xsi -eq "http://www.w3.org/2001/XMLSchema-instance" -and $xmlfile.instrumentationManifest.xs -eq "http://www.w3.org/2001/XMLSchema" -and $xmlfile.instrumentationManifest.trace -eq "http://schemas.microsoft.com/win/2004/08/events/trace" ) { # Loop through existing providers foreach ($provider in $xmlfile.instrumentationManifest.instrumentation.events.provider) { # Check Provider info if (-not $OnlyDLLPath -or $pscmdlet.ParameterSetName -like "ExplicitProperty") { if ($pscmdlet.ParameterSetName -like "General" -or "ProviderGUID" -in $Property) { if ([guid]::new($provider.guid)) { Write-PSFMessage -Level Debug -Message "GUID '$($provider.guid)' for provider '$($provider.name)' is valid" } else { Write-PSFMessage -Level Verbose -Message "Failed testing GUID '$($provider.guid)' for provider '$($provider.name)'" $isOK = $false } } if ($pscmdlet.ParameterSetName -like "General" -or "ProviderName" -in $Property) { if ($provider.name -match (Get-PSFConfigValue -FullName WinEventLogCustomization.MatchString.ProviderName)) { Write-PSFMessage -Level Debug -Message "Name for provider '$($provider.name)' is valid" } else { Write-PSFMessage -Level Verbose -Message "Failed testing provider name '$($provider.name)'. Name did not match '$(Get-PSFConfigValue -FullName WinEventLogCustomization.MatchString.ProviderName)'" $isOK = $false } } if ($pscmdlet.ParameterSetName -like "General" -or "ProviderSymbol" -in $Property) { if ($provider.symbol -match (Get-PSFConfigValue -FullName WinEventLogCustomization.MatchString.ProviderSymbol)) { Write-PSFMessage -Level Debug -Message "Providersymbol '$($provider.symbol)' is valid" } else { Write-PSFMessage -Level Verbose -Message "Failed testing provider symbol '$($provider.symbol)'. Name did not match '$(Get-PSFConfigValue -FullName WinEventLogCustomization.MatchString.ProviderSymbol)'" $isOK = $false } } } if ($pscmdlet.ParameterSetName -like "General" -or "ResourceFileName" -in $Property) { if (Test-Path -Path $provider.resourceFileName -PathType Leaf) { Write-PSFMessage -Level Debug -Message "Ressource file '$($provider.resourceFileName)' for provider '$($provider.name)' GUID:$($provider.guid) is valid" } else { Write-PSFMessage -Level Verbose -Message "Failed testing ressource file '$($provider.resourceFileName)' for provider '$($provider.name)' GUID:$($provider.guid)" $isOK = $false } } if ($pscmdlet.ParameterSetName -like "General" -or "MessageFileName" -in $Property) { if (Test-Path -Path $provider.messageFileName -PathType Leaf) { Write-PSFMessage -Level Debug -Message "Message file '$($provider.messageFileName)' for provider '$($provider.name)' GUID:$($provider.guid) is valid" } else { Write-PSFMessage -Level Verbose -Message "Failed testing message file '$($provider.messageFileName)' for provider '$($provider.name)' GUID:$($provider.guid)" $isOK = $false } } if ($pscmdlet.ParameterSetName -like "General" -or "ParameterFileName" -in $Property) { if (Test-Path -Path $provider.parameterFileName -PathType Leaf) { Write-PSFMessage -Level Debug -Message "Parameter file '$($provider.parameterFileName)' for provider '$($provider.name)' GUID:$($provider.guid) is valid" } else { Write-PSFMessage -Level Verbose -Message "Failed testing parameter file '$($provider.parameterFileName)' for provider '$($provider.name)' GUID:$($provider.guid)" $isOK = $false } } if (-not $OnlyDLLPath -or $pscmdlet.ParameterSetName -like "ExplicitProperty") { # Loop through channels within provider foreach ($channel in $provider.channels.channel) { # Check Channel info if ($pscmdlet.ParameterSetName -like "General" -or "ChannelName" -in $Property) { if ($channel.name -match (Get-PSFConfigValue -FullName WinEventLogCustomization.MatchString.ChannelName)) { Write-PSFMessage -Level Debug -Message "Name for ChannelName '$($channel.name)' in provider '$($provider.name)' is valid" } else { Write-PSFMessage -Level Verbose -Message "Failed testing ChannelName '$($channel.name)' in provider '$($provider.name)'. Name did not match '$(Get-PSFConfigValue -FullName WinEventLogCustomization.MatchString.ChannelName)'" $isOK = $false } } if ($pscmdlet.ParameterSetName -like "General" -or "ChannelSymbol" -in $Property) { if ($channel.symbol -match (Get-PSFConfigValue -FullName WinEventLogCustomization.MatchString.ChannelSymbol)) { Write-PSFMessage -Level Debug -Message "Name for ChannelSymbol '$($channel.symbol)' in provider '$($provider.name)' is valid" } else { Write-PSFMessage -Level Verbose -Message "Failed testing ChannelSymbol '$($channel.symbol)' in provider '$($provider.name)'. Name did not match '$(Get-PSFConfigValue -FullName WinEventLogCustomization.MatchString.ChannelSymbol)'" $isOK = $false } } if ($pscmdlet.ParameterSetName -like "General" -or "Enabled" -in $Property) { if (($channel.enabled -like [bool]::TrueString) -or ($channel.enabled -like [bool]::FalseString)) { Write-PSFMessage -Level Debug -Message "Value Enabled:'$($channel.enabled)' on channel '$($channel.name)' in provider '$($provider.name)' is valid" } else { Write-PSFMessage -Level Verbose -Message "Failed testing value '$($channel.enabled)' on channel '$($channel.name)' in provider '$($provider.name)'." $isOK = $false } } if ($pscmdlet.ParameterSetName -like "General" -or "Type" -in $Property) { if ($channel.type -in (Get-PSFConfigValue -FullName WinEventLogCustomization.MatchString.ChannelTypes)) { Write-PSFMessage -Level Debug -Message "Type '$($channel.type)' on channel '$($channel.name)' in provider '$($provider.name)' is valid" } else { Write-PSFMessage -Level Verbose -Message "Failed testing type '$($channel.type)' on channel '$($channel.name)' in provider '$($provider.name)'." $isOK = $false } } } } } } else { Write-PSFMessage -Level Error -Message "'$($file)' seeams like not being a Windows EventLog Channel XML manifest file" $isOK = $false } # Output result if ($PassThru -and $isOK) { $file } else { $isOK } } } end { } } function Unregister-WELCEventChannelManifest { <# .SYNOPSIS Unregister-WELCEventChannelManifest .DESCRIPTION Unregister a manifest and its compiled DLL file from windows EventLog sytem .PARAMETER Path The path to the manifest (and the dll) file .PARAMETER ComputerName The computer where to register the manifest file .PARAMETER Session PowerShell Session object where to unregister the manifest file .PARAMETER Credential The credentials to use on remote calls .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .NOTES Author: Andreas Bellstedt .LINK https://github.com/AndiBellstedt/WinEventLogCustomization .EXAMPLE PS C:\> Unregister-WELCEventChannelManifest -Path C:\CustomDLLPath\MyChannel.man Unregister the manfifest-file from Windows EventLog System, so it no longer appears in Application and Services Logs. .EXAMPLE PS C:\> Unregister-WELCEventChannelManifest -Path C:\CustomDLLPath\MyChannel.man -ComputerName SRV01 Unregister the manfifest-file from Windows EventLog System on the remote computer "SRV01". .EXAMPLE PS C:\> Unregister-WELCEventChannelManifest -Path C:\CustomDLLPath\MyChannel.man -Sesion $PSSession Unregister the manfifest-file from Windows EventLog System from all connections within the $PSSession variable Assuming $PSSession variable is created something like this: $PSSession = New-PSSession -ComputerName SRV01 #> [CmdletBinding( SupportsShouldProcess = $true, PositionalBinding = $true, ConfirmImpact = 'High', DefaultParameterSetName = 'ComputerName' )] param ( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [Alias("File", "FileName", "FullName")] [String] $Path, [Parameter( ParameterSetName = "ComputerName", ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 1 )] [Alias("Host", "Hostname", "Computer", "DNSHostName")] [PSFComputer[]] $ComputerName = $env:COMPUTERNAME, [Parameter( ParameterSetName = "Session", Position = 1 )] [System.Management.Automation.Runspaces.PSSession[]] $Session, [Parameter(ParameterSetName = "ComputerName")] [PSCredential] $Credential ) begin { # If session parameter is used -> transfer it to ComputerName, # The class "PSFComputer" from PSFramework can handle it. This simplifies the handling in the further process block if ($Session) { $ComputerName = $Session.ComputerName } $pathBound = Test-PSFParameterBinding -ParameterName Path $computerBound = Test-PSFParameterBinding -ParameterName ComputerName } process { #region parameterset workarround Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($PsCmdlet.ParameterSetName)" # Workarround parameter binding behaviour of powershell in combination with ComputerName Piping if (-not ($pathBound -or $computerBound) -and $ComputerName.InputObject -and $PSCmdlet.ParameterSetName -ne "Session") { if ($ComputerName.InputObject -is [string]) { $ComputerName = $env:ComputerName } else { $Path = "" } } #endregion parameterset workarround #region Processing Events foreach ($file in $Path) { # File/path validation if (-not (Test-Path -Path $file -PathType Leaf -IsValid)) { Write-PSFMessage -Level Error -Message"'$($file)' is not a valid path or file." continue } else { Write-PSFMessage -Level Debug -Message "Working on file '$($file)'" } # Process computers foreach ($computer in $ComputerName) { Write-PSFMessage -Level Verbose -Message "Processing file '$($file)' on computer '$($computer)'" # When remoting is used, transfer files first if (($PSCmdlet.ParameterSetName -eq "Session") -or (-not $computer.IsLocalhost)) { # Create PS remoting session, if no session exists if ($PSCmdlet.ParameterSetName -ne "Session") { $paramSession = @{ "ComputerName" = $computer.ToString() "ErrorAction" = "Stop" } if ($Credential) { $paramSession.Add("Credential", $Credential) } try { $Session = New-PSSession @paramSession Write-PSFMessage -Level Debug -Message "New remoting session created to '$($Session.ComputerName)'" } catch { Write-PSFMessage -Level Error -Message "Error creating remoting session to computer '$($computer)'" -Target $computer -ErrorRecord $_ break } } } # Register manifest if ($pscmdlet.ShouldProcess("Manifest '$($Path)' from computer '$($computer)'", "Unregister")) { $paramInvokeCmd = [ordered]@{ "ComputerName" = $computer.ToString() "ErrorAction" = "Stop" ErrorVariable = "ErrorReturn" "ArgumentList" = $file } if ($PSCmdlet.ParameterSetName -eq "Session") { $paramInvokeCmd['ComputerName'] = $Session } if ($Credential) { $paramInvokeCmd.Add("Credential", $Credential) } Write-PSFMessage -Level Verbose -Message "Unregistering manifest '$($file)' from computer '$($computer)'" -Target $computer try { $null = Invoke-PSFCommand @paramInvokeCmd -ScriptBlock { try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch { Write-Information -MessageData "Exception while setting UTF8 OutputEncoding. Continue script." } $output = . "$($env:windir)\system32\wevtutil.exe" "uninstall-manifest" "$($args[0])" *>&1 $output = $output | Where-Object { $_.InvocationInfo.MyCommand.Name -like 'wevtutil.exe' } *>&1 if ($output) { Write-Error -Message "$([string]::Join(" ", $output.Exception.Message.Replace("`r`n"," ")))" -ErrorAction Stop } } if ($ErrorReturn) { Write-Error "Error registering manifest" -ErrorAction Stop } } catch { Stop-PSFFunction -Message "Error unregistering manifest '$($file)' on computer '$($computer)'" -Target $computer -ErrorRecord $_ } } } } } end { } } <# This is an example configuration file By default, it is enough to have a single one of them, however if you have enough configuration settings to justify having multiple copies of it, feel totally free to split them into multiple files. #> <# # Example Configuration Set-PSFConfig -Module 'WinEventLogCustomization' -Name 'Example.Setting' -Value 10 -Initialize -Validation 'integer' -Handler { } -Description "Example configuration setting. Your module can then use the setting using 'Get-PSFConfigValue'" #> Set-PSFConfig -Module 'WinEventLogCustomization' -Name 'Import.DoDotSource' -Value $false -Validation 'bool' -Description "Whether the module files should be dotsourced on import. By default, the files of this module are read as string value and invoked, which is faster but worse on debugging." -Initialize Set-PSFConfig -Module 'WinEventLogCustomization' -Name 'Import.IndividualFiles' -Value $false -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments." -Initialize Set-PSFConfig -Module 'WinEventLogCustomization' -Name 'MatchString.ProviderName' -Value '(^([a-zA-Z]| |[0-9]|\(|\))*$)|(^([a-zA-Z]| |[0-9]|\(|\))*-([a-zA-Z]| |[0-9]|\(|\))*-([a-zA-Z]| |[0-9]|\(|\))*$)' -Description "Regex to validate name of ProviderName within a manifest file" -Initialize Set-PSFConfig -Module 'WinEventLogCustomization' -Name 'MatchString.ProviderSymbol' -Value '(^([a-zA-Z]| |[0-9]|\(|\))*$)|(^([a-zA-Z]| |[0-9]|\(|\))*_([a-zA-Z]| |[0-9]|\(|\))*_([a-zA-Z]| |[0-9]|\(|\))*$)|(^([a-zA-Z]| |[0-9]|\(|\))*_([a-zA-Z]| |[0-9]|\(|\))*_([a-zA-Z]| |[0-9]|\(|\))*_([a-zA-Z]| |[0-9]|\(|\))*$)'-Description "Regex to validate name of ProviderSymbol within a manifest file" -Initialize Set-PSFConfig -Module 'WinEventLogCustomization' -Name 'MatchString.ChannelName' -Value '(^(\w| )*\/(\w| |\(|\))*$)|(^(\w| )*-(\w| )*-(\w| )*\/(\w| |\(|\))*$)'-Description "Regex to validate name of ChannelName within a manifest file" -Initialize Set-PSFConfig -Module 'WinEventLogCustomization' -Name 'MatchString.ChannelSymbol' -Value '(^([a-zA-Z]|[0-9]|\(|\))*_([a-zA-Z]|[0-9]|\(|\))*$)|(^([a-zA-Z]|[0-9]|\(|\))*_([a-zA-Z]|[0-9]|\(|\))*_([a-zA-Z]|[0-9]|\(|\))*_([a-zA-Z]|[0-9]|\(|\))*$)' -Description "Regex to validate name of ChannelSymbol within a manifest file" -Initialize Set-PSFConfig -Module 'WinEventLogCustomization' -Name 'MatchString.ChannelTypes' -Value @("Admin", "Operational", "Analytic", "Debug") -Validation stringarray -Description "Name-array of possible ChannelTypes" -Initialize <# Stored scriptblocks are available in [PsfValidateScript()] attributes. This makes it easier to centrally provide the same scriptblock multiple times, without having to maintain it in separate locations. It also prevents lengthy validation scriptblocks from making your parameter block hard to read. Set-PSFScriptblock -Name 'WinEventLogCustomization.ScriptBlockName' -Scriptblock { } #> Register-PSFTeppScriptblock -Name "WinEventLogCustomization.ChannelFullName" -ScriptBlock { Get-WinEvent -ListLog * -ErrorAction Ignore | Select-Object -ExpandProperty LogName } Register-PSFTeppScriptblock -Name "WinEventLogCustomization.Bool" -ScriptBlock { @( '$true', '$false' ) } Register-PSFTeppScriptblock -Name "WinEventLogCustomization.MaxEventLogSize" -ScriptBlock { @( '16MB', '64MB', '128MB', '512MB', '1GB', '2GB', '5GB', '10GB' ) } Register-PSFTeppScriptblock -Name "WinEventLogCustomization.FolderRoot" -ScriptBlock { Get-WinEvent -ListLog *-* -ErrorAction Ignore | Select-Object -ExpandProperty LogName | ForEach-Object { $_.split("-")[0] } | Sort-Object -Unique } Register-PSFTeppScriptblock -Name "WinEventLogCustomization.FolderSecondLevel" -ScriptBlock { Get-WinEvent -ListLog *-*-* -ErrorAction Ignore | Select-Object -ExpandProperty LogName | ForEach-Object { $_.split("-")[1] } | Sort-Object -Unique } Register-PSFTeppScriptblock -Name "WinEventLogCustomization.FolderThirdLevel" -ScriptBlock { Get-WinEvent -ListLog *-*-* -ErrorAction Ignore | Select-Object -ExpandProperty LogName | ForEach-Object { $_.split("-")[2].split("/")[0] } | Sort-Object -Unique } Register-PSFTeppScriptblock -Name "WinEventLogCustomization.ChannelName" -ScriptBlock { Get-WinEvent -ListLog */* -ErrorAction Ignore | Select-Object -ExpandProperty LogName | ForEach-Object { $_.split("/")[1] } | Sort-Object -Unique } # Get-WELCEventChannel Register-PSFTeppArgumentCompleter -Command Get-WELCEventChannel -Parameter "ChannelFullName" -Name "WinEventLogCustomization.ChannelFullName" # Set-WELCEventChannel Register-PSFTeppArgumentCompleter -Command Set-WELCEventChannel -Parameter "ChannelFullName" -Name "WinEventLogCustomization.ChannelFullName" Register-PSFTeppArgumentCompleter -Command Set-WELCEventChannel -Parameter "Enabled" -Name "WinEventLogCustomization.Bool" Register-PSFTeppArgumentCompleter -Command Set-WELCEventChannel -Parameter "CompressLogFolder" -Name "WinEventLogCustomization.Bool" Register-PSFTeppArgumentCompleter -Command Set-WELCEventChannel -Parameter "AllowFileAccessForLocalService" -Name "WinEventLogCustomization.Bool" Register-PSFTeppArgumentCompleter -Command Set-WELCEventChannel -Parameter "MaxEventLogSize" -Name "WinEventLogCustomization.MaxEventLogSize" # New-WELCEventChannelManifest Register-PSFTeppArgumentCompleter -Command New-WELCEventChannelManifest -Parameter "FolderRoot" -Name "WinEventLogCustomization.FolderRoot" Register-PSFTeppArgumentCompleter -Command New-WELCEventChannelManifest -Parameter "FolderSecondLevel" -Name "WinEventLogCustomization.FolderSecondLevel" Register-PSFTeppArgumentCompleter -Command New-WELCEventChannelManifest -Parameter "FolderThirdLevel" -Name "WinEventLogCustomization.FolderThirdLevel" Register-PSFTeppArgumentCompleter -Command New-WELCEventChannelManifest -Parameter "ChannelName" -Name "WinEventLogCustomization.ChannelName" New-PSFLicense -Product 'WinEventLogCustomization' -Manufacturer 'Andreas Bellstedt' -ProductVersion $script:ModuleVersion -ProductType Module -Name MIT -Version "1.0.0.0" -Date (Get-Date "2022-06-26") -Text @" Copyright (c) 2022 Andreas Bellstedt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. "@ #endregion Load compiled code |