functions/Register-WELCEventChannelManifest.ps1

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 {
    }
}