PowerShell Write-Log: 一个简单的日志记录函数教程

如果您编写的PowerShell脚本具有任何实际意义,您需要记录日志。无论您是部署软件、管理服务还是自动化任务,记录脚本执行情况(或未执行情况)都至关重要。在本教程中,您将学习如何创建一个简单但有效的PowerShell记录函数。

先决条件

如果您想跟随本教程操作,请确保您具备以下条件:

  • Windows 10或带有PowerShell 5.1或PowerShell 7+的Windows Server
  • 文本编辑器(推荐使用VSCode)
  • 基本了解PowerShell函数

基本记录的问题

假设您正在编写一个脚本来静默安装某些软件。基本方法可能如下所示:

Add-Content -Path "C:\\Scripts\\install.log" -Value "Starting install..."
Start-Process -FilePath 'installer.exe' -ArgumentList '/i /s' -Wait -NoNewWindow
Add-Content -Path "C:\\Scripts\\install.log" -Value "Finished install."

这个方法虽然可行,但存在一些问题:

  • 没有时间戳
  • 重复的代码
  • 不一致的记录格式
  • 硬编码记录路径

让我们通过构建一个适当的记录函数来解决这些问题。

构建一个基本的Write-Log函数

首先,让我们创建一个简单的函数,为我们的日志条目添加时间戳:

function Write-Log {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Message
    )

    $timeGenerated = Get-Date -Format HH:mm:ss
    Add-Content -Path "C:\\Scripts\\script.log" -Value "$timeGenerated - $Message"
}

现在您可以像这样使用它:

Write-Log -Message "Starting install..."
Start-Process -FilePath 'installer.exe' -ArgumentList '/i /s' -Wait -NoNewWindow
Write-Log -Message "Finished install."

日志文件(C:\Scripts\script.log)将包含如下条目:

09:42:15 - Starting install...
09:43:22 - Finished install.

更清晰!但我们可以做得更好。

添加更多功能

让我们通过一些有用的功能增强我们的记录函数:

  • 自定义记录路径
  • 不同的日志级别(信息,警告,错误)
  • 文件名中包含日期
  • 错误处理

这是改进版本:

function Write-Log {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Message,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$LogFilePath = "C:\\Scripts\\Logs",

        [Parameter()]
        [ValidateSet('Information','Warning','Error')]
        [string]$Level = "Information"
    )

    # Create the log directory if it doesn't exist
    if (!(Test-Path $LogFilePath)) {
        New-Item -Path $LogFilePath -ItemType Directory -Force | Out-Null
    }

    # Build the log file path with date
    $date = Get-Date -Format "MM-dd-yyyy"
    $logFile = Join-Path $LogFilePath "log-$date.txt"

    # Get the current timestamp
    $timeStamp = Get-Date -Format "HH:mm:ss"

    # Create the log entry
    $logEntry = "$timeStamp [$Level] - $Message"

    try {
        Add-Content -Path $logFile -Value $logEntry -ErrorAction Stop
    }
    catch {
        Write-Error "Failed to write to log file: $_"
    }
}

这个增强版为您提供了更多灵活性。以下是如何使用它:

# Basic information logging
Write-Log -Message "Starting software installation"

# Warning about a non-critical issue
Write-Log -Message "Config file not found, using defaults" -Level Warning

# Log an error
Write-Log -Message "Installation failed!" -Level Error

# Use a custom log path
Write-Log -Message "Custom path log" -LogFilePath "D:\\CustomLogs"

生成的日志文件(log-03-12-2024.txt)将如下所示:

10:15:22 [Information] - Starting software installation
10:15:23 [Warning] - Config file not found, using defaults
10:15:25 [Error] - Installation failed!

并位于 D:\CustomLogs\log-03-12-2024.txt:

10:15:26 [Information] - Custom path log

请注意,每个条目都包括时间戳、日志级别(括在方括号中)和消息。这种结构化格式使得解析日志并快速识别问题变得容易。

实际示例:软件安装脚本

让我们在一个真实的安装软件的脚本中使用我们的记录函数:

# First, dot-source the logging function
. .\\Write-Log.ps1

# Script variables
$installer = "C:\\Installers\\software.exe"
$logPath = "C:\\Scripts\\InstallLogs"

# Start logging
Write-Log -Message "Beginning installation process" -LogFilePath $logPath

# Check if installer exists
if (Test-Path $installer) {
    Write-Log -Message "Found installer at: $installer"

    try {
        # Attempt installation
        Write-Log -Message "Starting installation..."
        $process = Start-Process -FilePath $installer -ArgumentList '/i /s' -Wait -NoNewWindow -PassThru

        # Check the exit code
        if ($process.ExitCode -eq 0) {
            Write-Log -Message "Installation completed successfully"
        }
        else {
            Write-Log -Message "Installation failed with exit code: $($process.ExitCode)" -Level Error
        }
    }
    catch {
        Write-Log -Message "Installation failed with error: $_" -Level Error
    }
}
else {
    Write-Log -Message "Installer not found at: $installer" -Level Error
}

Write-Log -Message "Installation script completed"

生成的日志文件将类似于这样:

09:15:22 [Information] - Beginning installation process
09:15:22 [Information] - Found installer at: C:\\Installers\\software.exe
09:15:22 [Information] - Starting installation...
09:16:45 [Information] - Installation completed successfully
09:16:45 [Information] - Installation script completed

有用提示

在使用此记录函数时,请遵循以下最佳实践:

  1. 始终记录脚本的开始和结束 – 这有助于跟踪脚本执行时间和完成状态。

  2. 使用适当的日志级别 – 不要将所有内容标记为错误;根据情况使用正确的级别:

    • 信息:正常操作
    • 警告:非关键问题
    • 错误:需要关注的关键问题
  3. 包含相关细节 – 记录足够的信息以了解发生了什么:

    # 差
    Write-Log "连接失败"
    
    # 好
    Write-Log "连接到服务器 'SQL01' 失败 - 30秒超时" -Level Error
    
  4. 清理旧日志 – 考虑添加日志轮换以防止磁盘空间填满:

    # 删除早于30天的日志
    Get-ChildItem -Path $LogFilePath -Filter "*.txt" |
        Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
        Remove-Item
    

结论

一个良好的日志记录函数对于任何严肃的 PowerShell 脚本都是必不可少的。有了我们构建的Write-Log函数,您现在可以灵活地和可重用地为所有脚本添加适当的日志记录。记得根据您的具体需求调整函数 – 您可能想要添加诸如:

日志轮换

function Write-Log {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Message,

        [Parameter()]
        [int]$MaxLogFiles = 30  # Keep last 30 days of logs
    )

    # Remove old log files
    Get-ChildItem -Path $LogFilePath -Filter "*.txt" |
        Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$MaxLogFiles) } |
        Remove-Item -Force

    # Continue with normal logging...
}

不同的输出格式(CSV、JSON)

function Write-Log {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Message,

        [Parameter()]
        [ValidateSet('TXT','CSV','JSON')]
        [string]$Format = 'TXT'
    )

    $logEntry = [PSCustomObject]@{
        Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
        Level = $Level
        Message = $Message
    }

    switch ($Format) {
        'CSV'  { $logEntry | Export-Csv -Path "$LogFilePath\\log.csv" -Append -NoTypeInformation }
        'JSON' { $logEntry | ConvertTo-Json | Add-Content -Path "$LogFilePath\\log.json" }
        'TXT'  { "$($logEntry.Timestamp) [$($logEntry.Level)] - $($logEntry.Message)" |
                 Add-Content -Path "$LogFilePath\\log.txt" }
    }
}

网络路径支持

function Write-Log {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Message,

        [Parameter()]
        [string]$NetworkPath = "\\\\server\\logs"
    )

    # Test network path connectivity
    if (!(Test-Path $NetworkPath)) {
        # Fallback to local logging if network is unavailable
        $NetworkPath = "C:\\Scripts\\Logs"
        Write-Warning "Network path unavailable. Using local path: $NetworkPath"
    }

    # Continue with normal logging...
}

错误的电子邮件通知

function Write-Log {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Message,

        [Parameter()]
        [string]$SmtpServer = "smtp.company.com",

        [Parameter()]
        [string[]]$NotifyOnError = "[email protected]"
    )

    # Normal logging first...

    # Send email if this is an error
    if ($Level -eq 'Error' -and $NotifyOnError) {
        $emailParams = @{
            From = "[email protected]"
            To = $NotifyOnError
            Subject = "PowerShell Script Error"
            Body = "Error occurred at $timeStamp`n`nMessage: $Message"
            SmtpServer = $SmtpServer
        }

        try {
            Send-MailMessage @emailParams
        }
        catch {
            Write-Warning "Failed to send error notification: $_"
        }
    }
}

关键是从一个坚实的基础开始,并根据您的具体需求逐步构建。这些示例应该为您扩展基本日志记录功能以添加更高级功能提供了一个良好的起点。

例如,您可以将这些功能组合成一个综合的日志记录解决方案:

Write-Log -Message "Critical error in payment processing" `
          -Level Error `
          -Format CSV `
          -NetworkPath "\\\\server\\logs" `
          -NotifyOnError "[email protected]","[email protected]" `
          -MaxLogFiles 90

这将:

  • 以 CSV 格式记录错误
  • 将其存储在网络共享上
  • 向多个收件人发送电子邮件
  • 保留 90 天的日志历史记录

请务必进行彻底测试,特别是在实施网络路径或电子邮件通知时,因为这些外部依赖可能会影响脚本的可靠性。祝您编写脚本愉快!

Source:
https://adamtheautomator.com/powershell-write-log-tutorial/