Deploy log4net for ControlUp processes

ControlUp binaries by default generate minimum logging to prevent disk exhaustion.
To enable DEBUG logs, deploying the log4net file in the same directory as a ControlUp process initiates the capture.
This script will initiate the logging process and allows placing of the log file in a custom location.
If log duration is greater than zero, the log capture with stop at the end of the period.

Support Mode changes the format of the lines in the resultant log file to make it easier to merge multiple log files, where required.
Version 2.10.56
Created on 2020-10-01
Modified on 2024-05-26
Created by Marcel Calef
Downloads: 166

The Script Copy Script Copied to clipboard
<#
.SYNOPSIS
    Create a log file for a ControlUp Monitor by creating a Log4net file.
.DESCRIPTION
    Creates a log file with a user-defined logfile and script duration.

.PARAMETER logPath
    The folder in which to create the log file

.PARAMETER exename
    The ControlUp executable to create the log file for

.PARAMETER logDurationSeconds
    Number of seconds to allow log file to colect data

.PARAMETER loglevel
    The logging level to set in the log file
    
.PARAMETER FinalLogLevel
    The logging level to set in the log file once the logging period has finished. FATAL or OFF are recommended as continual logging can cause slowness

.EXAMPLE
    .\log4net.ps1 -LogPath "C:\temp" -logDuration 5 -logPath "C:\temp" -exeName "cuMonitor.exe"
    Saves a log file as cuMonitor.exe.log in the C:\tmp folder. The log4net file is saved for 5 seconds, after that it gets deleted.
    
.NOTES
    Modification History

    2022/11/07  Marcel Calef    Initial Version
    2022/11/20  Benjamin Skoda  Updates
    2022/12/12  Guy Leech       Parameter changes
    2022/12/13  Guy Leech       Changed log file name to include date, time and computer name. Added -FinalLogLevel, disabled nulling of log file name
    2024/02/28  Guy Leech       Added -supportMode to output log lines in a different format. Trimming quotes off log path
#>

[CmdletBinding()]

param (
    [ValidateNotNullorEmpty()]
    [String]$LogPath = 'c:\temp',

    [ValidateSet('cuMonitor.exe','cuagent.exe')]
    [String]$exeName = 'cuMonitor.exe', 

    [uint16]$logDurationSeconds = 60 ,

    [ValidateSet( 'DEBUG', 'ERROR', 'FATAL', 'ALL', 'VERBOSE', 'INFO', 'OFF' )]
    [string]$LogLevel = 'ERROR' ,
   
    [ValidateSet( 'DEBUG', 'ERROR', 'FATAL', 'ALL', 'VERBOSE', 'INFO', 'OFF' )]
    [string]$FinalLogLevel = 'FATAL' ,
   
    [ValidateSet( 'yes','no' )]
    [string]$supportMode = 'no'
)

$DebugPreference = $(if( $PSBoundParameters[ 'debug' ] ) { 'Continue' } else { 'SilentlyContinue' })
$VerbosePreference = $(if( $PSBoundParameters[ 'verbose' ] ) { 'Continue' } else { 'SilentlyContinue' })
$ErrorActionPreference = $(if( $PSBoundParameters[ 'ErrorAction' ] ) { $ErrorActionPreference } else { 'Stop' })

[int]$outputWidth = 400
try
{
    if( ( $PSWindow = (Get-Host).UI.RawUI ) -and ( $WideDimensions = $PSWindow.BufferSize ) )
    {
        Write-Verbose -Message "Setting output width to $outputWidth"
        $WideDimensions.Width = $outputWidth
        $PSWindow.BufferSize = $WideDimensions
        Write-Verbose -Message "Set output width to $($WideDimensions.width)"
    }
}
catch
{
    ## not much we can do but will hide the error since it is not fundamental to script functionality, just output
    Write-Warning -Message "Failed to set output width to $($WideDimensions.width) : $_"
}

[string]$conversionPattern = "'%date',%logger,'[%thread]','%level','%message%'%newline"

if( $supportMode -ieq 'yes' ) {
    $conversionPattern = "%date{yyyy-MM-dd HH:mm:ss.fff};[%level];%property{log4net:HostName};%appdomain;%logger;%stacktrace;%message;%newline"
}

[XML]$log4net = @"
<?xml version="1.0" encoding="UTF-8"?>
    <!--
        comments ommited
     -->
    <log4net>
      <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
      <file value="LogFile.log"/>
      <appendToFile value="true"/>
      <rollingStyle value="Size"/>
      <maxSizeRollBackups value="2"/>
      <maximumFileSize value="100MB"/>
      <staticLogFileName value="true"/>
            <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
      <layout type="log4net.Layout.PatternLayout">
       <conversionPattern value="$conversionPattern"/>
      </layout>
     </appender>
      <root>
      <level value="DEBUG"/>
        <appender-ref ref="RollingFileAppender"/>
     </root>
    </log4net>
"@

## CU agent can quote paths so we unquote
$logPath = $logPath.Trim( '" ' )

##Write-Host "###########################"
Write-Host "User Inputs":
Write-Host "LogLevel set: $LogLevel"
Write-Host "Name of CU component: $($exeName)" 
##Write-Host "Path of CU components : $($exePath)" 
Write-Host "Run the logger for $logDurationSeconds seconds"
Write-Host "Log path is $LogPath"
##Write-Host "###########################"

if ( -Not ( Test-Path $LogPath -PathType Container)  ){
    if( -Not ( New-Item $logPath -ItemType Directory -Force ) ) {
        Throw "Problem creating log folder `"$logPath`""
    }
}

Function Set-LogLevel {
    param(
        [Parameter(Mandatory = $true)]
        [String]$LogLevel
    )
    
    $log4net.log4net.root.level.value = $LogLevel
}

Function Set-FileNameInLog {
    param (
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [String]$LogName
    )

    $log4net.log4net.appender.file.value = $LogName
}

function Create-LogFile {
    param (
        [String]$path ,
        [String]$exename
    )

    $exepath = Join-Path -Path $path -ChildPath $exename
    $global:PathToCheck = "$($exePath).log4net"

    try {
        $log4net.Save( $global:PathToCheck )
    }
    catch {
        Write-Warning "Failed write to log4net file `"$global:PathToCheck`" : $_"
    }
}

function Start-Logging {
    param (
        [Parameter(Mandatory = $true)]
        [Int]$logDurationSeconds ,
        [switch]$removeLog4netFile
    )

    Write-Verbose -Message "$(Get-Date -Format G): sleeping for $logDurationSeconds seconds"

    Start-sleep -Seconds $logDurationSeconds

    if( $removeLog4netFile ) {
        Remove-Item -Path $global:PathToCheck -Force -Confirm:$false
    }
}

function Add-RequiredACL { 
    param ($Object, 
        [System.Security.Principal.NTAccount]$Identity, 
        [System.Security.AccessControl.FileSystemRights]$AccessMask, 
        [System.Security.AccessControl.AccessControlType]$Type) 
    
    $InheritanceFlags = [System.Security.AccessControl.InheritanceFlags]'ContainerInherit, ObjectInherit'
    $PropagationFlags = [System.Security.AccessControl.PropagationFlags]'None'
    $SD = $null
    $SD = get-acl -Path $Object
    if( $SD ) {
        $Rule = new-object -Typename System.Security.AccessControl.FileSystemAccessRule -argumentlist @($Identity, $AccessMask, $InheritanceFlags, $PropagationFlags, $Type) 
        $SD.AddAccessRule( $Rule ) 
        set-acl -Path $Object -AclObject $SD 
    } else {
        Write-Warning -Message "Failed to get security descriptor for `"$object`" so may not be able to write to it"
    }
}

function Restart-CUService {
    [CmdletBinding()]
    param (
        [Parameter()]
        [String]$exeName
    )
    
    [string]$ServiceName = (Split-Path -Path $exeName -Leaf) -replace '\.[^\.]+$' ## strip last extension
    if ($ServiceName -imatch 'cuMonitor|cuAgent' ) {
        $serviceHandle = $null
        Write-Verbose -Message "$(Get-Date -Format G): restarting service $ServiceName"
        $serviceHandle = restart-service -Name $ServiceName -PassThru
        if( -Not $serviceHandle ) {
            Write-Warning -Message "Failed restart of service $ServiceName"
        }
        elseif( $serviceHandle.Status -ine 'Running' ) {
            Write-Warning -Message "Problem with restart of service $ServiceName - status is $($serviceHandle.Status)"
        }
    }
    ## not a service 
    Write-Host "$(Get-Date -Format G): Service $ServiceName restarted"
}

Function Get-ExePath {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]$exeName
    )
    [string]$exePath = $null
    if( $exeName -match '^(cumonitor|cuagent)' ) {
        [string]$servicename = $Matches[1]
        $service = $null
        $service = Get-WmiObject -Class win32_service -Filter "name = '$servicename'"
        if( -Not $service ) {
            Write-Error "Unable to find definition for service $servicename"
        }
        else {
            ## "C:\Program Files\Smart-X\ControlUpAgent\Version 8.6.5.465\cuAgent.exe" /service
            if( $service.PathName -match '^"([^"]+)"' -or $service.PathName -match '^(.*)\s' ) {
                $exePath = Split-Path -Path $Matches[1] -Parent
            } else {
                Write-Warning -Message "Unable to extract exe folder from `"$($service.PathName)`""
            }
        }
    }
    elseif( $exeName -match 'cuconsole' ) {
        ## TODO get path from current running process
    }
    else {
        Write-Warning "Do not know what to do to get exe path for $exeName"
    }
    $exePath ## return
}
        
try {
    ## use well known SID for "NT AUTHORITY\NETWORK SERVICE" to avoid language issues https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers
    Add-RequiredACL -object $LogPath -Identity  ([System.Security.Principal.SecurityIdentifier]('S-1-5-20')).Translate([System.Security.Principal.NTAccount]).Value -AccessMask Modify, Synchronize -Type Allow
}
catch {
    Write-Error $_.Exception.Message
}

Set-LogLevel -LogLevel $LogLevel
[string]$exePath = Get-ExePath -exeName $exeName
if( $exePath ) {
    [string]$logfileFullPath = Join-Path -Path $LogPath -ChildPath "$($exeName)_$($env:COMPUTERNAME)_$([datetime]::Now.ToString( 'yyyy-MM-dd_HH-mm-ss' )).log"
    Set-FileNameInLog -LogName $logfileFullPath
    Write-Host "Path of logfile: $($logfileFullPath)" 
    Write-Verbose -Message "exe path is `"$exePath`""
    if( Test-Path -Path $logfileFullPath -PathType Leaf ) {
        $moveError = $null
        if( Move-Item -Path $logfileFullPath -Destination "$($logfileFullPath).old" -PassThru -ErrorAction SilentlyContinue -ErrorVariable moveError) {
            Write-Host "Moved previous log file to `"$($logfileFullPath).old`""
        }
        else {
            Write-Warning -Message "Problem backing up existing log file to `"$($logfileFullPath).old`" : $moveError"
        }
    }
    Create-LogFile -path $exePath -exename $exeName
    Start-Logging -LogDuration $logDurationSeconds
    ## Don't restart as will cause console to lose connection if it is the cuagent since the console is communicating with it to run the script
    ## Restart-CUService -exeName $exeName
    Set-LogLevel -LogLevel $FinalLogLevel
    ## Set-FileNameInLog -LogName ''
    $log4net.Save( $global:PathToCheck )
}
## else will already have given error/reason