带交换的Powershell - 如何将所有详细输出附加到文件

时间:2017-01-25 20:18:05

标签: powershell office365 exchange-server

我正在尝试在我的脚本中添加一些日志记录。任何建议将不胜感激。首先,我想在出现问题时添加错误日志。

例如,当找不到用户时会抛出以下错误:

无法执行此操作,因为对象' asdfa'无法在' HQ-DC-6.domain.com'上找到。     + CategoryInfo:NotSpecified:(0:Int32)[Set-RemoteMailbox],ManagementObjectNotFoundException     + FullyQualifiedErrorId:47285FC7,Microsoft.Exchange.Management.RecipientTasks.SetRemoteMailbox     + PSComputerName:hq-cas2.domain.com

==============================

$users = ForEach ($user in $(Get-Content 'C:\Users\test\Documents\Powershell Scripts\OffboardUsers.txt')) {

$tmpname = Get-RemoteMailbox -Identity $user | Select-Object -ExpandProperty Name
$tmpDisplayName = Get-RemoteMailbox -Identity $user | Select-Object -ExpandProperty DisplayName

Set-RemoteMailbox -Identity $user -Name ("_" + "$tmpname") >> error.log
Set-RemoteMailbox -Identity $user -DisplayName ("_" + "$tmpDisplayName") >> error.log
Set-RemoteMailbox -Identity $user -HiddenFromAddressListsEnabled $true >> error.log

}

2 个答案:

答案 0 :(得分:2)

本文已完全修订2017-07-18,因为新的Log-Entry解决方案取代了以前的Write-Log解决方案,该解决方案将不会进一步更新。另请参阅:Migrating from Write-Log

通常,我发现Microsoft脚本语言的日志记录被低估了。不仅在脚本(或cmdlet)的设计时,日志就派上用场,但是当脚本部署并出现问题时,您通常希望您有更好的日志记录。
这就是为什么我认为脚本语言如PowerShell(以及它的前身VBScript)实际上应该具有比现在更加复杂的本机日志记录功能。

最佳实践

即使在PowerShell存在之前,我对VBScript中的适当日志记录功能也有类似的需求。事实上,我用于VBScript的一些概念,我仍然在PowerShell中使用。同时,我已经扩展了我的日志记录解决方案,其中包含一系列改进和要求,因为我希望日志功能是:

  • 强大且永远不会导致实际的cmdlet意外失败(甚至是 例如,由于某种原因拒绝访问日志文件)

  • 简单地调用并可能用作
    Write-Host命令替换

  • 解析所有数据类型并显示内容

  • 捕获意外的原生脚本错误

  • 能够传递对象以进行内联日志记录,以最大限度地减少其他代码行

  • 对于性能问题,每个条目都有准确的(10毫秒)时间戳 拍摄

  • 标准捕获故障排除信息,如:

    • 脚本版

    • PowerShell版本

    • 运行时(处理开始时间)

    • 如何(参数)以及从哪里(位置)运行

  • 附加附加信息到可配置的日志文件,该日志文件无法无限增长

  • 向下兼容PowerShell版本2

鲁棒

如果您想使用强大的日志记录解决方案,您可能希望使用本机Start-Transcript cmdlet,但您可能会发现Start-Transcript缺少您可能期望的功能,例如时间戳。一个正确的日志记录cmdlet。您可以选择第三方解决方案,但这通常意味着额外的安装过程和依赖关系 因此,您决定自己编写,但即使是最简单的解决方案,您只是将信息写入文件可能已经导致现场问题:文件可能无法访问。它甚至可能存在,但是您的脚本被触发两次,并且多个实例在一个实例打开日志文件的同时运行,而另一个实例拒绝访问(参见例如:Powershell Scheduled Tasks conflicts?)。此时,日志记录实际上应该可以帮助您解决正在发生的事情,因为重复触发器也可能导致脚本本身出现意外行为。对于这个特定的例子,我在这里提出的解决方案缓冲输出,直到它能够写入。但是在编写日志记录cmdlet和正确格式化输出时会有更多陷阱。

登入输入

我已将整个解决方案放在由Log-Entry.ps1 framework组成的几个主要部分中:

  1. 帮助标题 - 以及带有一些示例的Main功能模板
  2. 包含一些脚本和日志记录定义的My对象
  3. 控制日志记录的四个功能:
    • Log-Entry(别名Log)来记录信息和对象
    • Set-LogFile(别名LogFile)设置日志文件的位置
    • End-Script(别名End)可能会用来很好地关闭会话
    • ConvertTo-Text(别名CText)来解析对象
  4. 有关最新的Log-Entry.ps1版本,请参阅:https://github.com/iRon7/Log-Entry

    用法

    下载上面的Log-Entry.ps1 framwork并使用您自己的脚本替换Main {}函数中的示例。您希望显示和记录信息的任何地方,使用Log命令(类似于Write-Host命令语法)。
    运行脚本并检查日志文件:%Temp%\<ScriptName>.Log

    语法

    有关语法的详细信息,请参阅:readme.md上的https://github.com/iRon7/Log-Entry

    示例

    以下是一些显示Log-Entry框架的一些功能的命令:

    LogFile .\Test.log                  # Redirect the log file location (Optional)
    Log -Color Yellow "Examples:"
    Log "Several examples that usually aren't displayed by Write-Host:" $NotSet @() @(@()) @(@(), @()) @($Null)
    Log -Indent 1 "Note 1: An empty string:" "" "isn't displayed by Log-Entry either (as you usually do not want every comment quoted)."
    Log -Indent 2 "In case you want to reveal a (possible) empty string, use -QuoteString:" -NoNewline; Log -QuoteString ""
    Log -Indent 1 "Note 2: An empty array embedded in another array:" @(@()) "is flattened by PowerShell (and not Write-Log)."
    Log -Indent 2 "To prevent this use a comma in front of the embbed array: " @(,@())
    Log "A hashtable:" @{one = 1; two = 2; three = 3}
    Log "A recursive hashtable:" @{one = @{one = @{one = 1; two = 2; three = 3}; two = 2; three = 3}; two = 2; three = 3} -Expand -Depth:9
    Log "Character array:" "Hallo World".ToCharArray()
    Log-Verbose "The following line produces a error which is captured in the log file:"
    $File = Log "File:" (Get-ChildItem "C:\NoSuchFile.txt" -ErrorAction SilentlyContinue)
    Log-Verbose "The switch -FlushErrors prevents the error being logged:"
    $File = Log "File:" (Get-ChildItem "C:\NoSuchFile.txt" -ErrorAction SilentlyContinue) -FlushErrors
    Log "Below are two inline log examples (the object preceding the ""?"" is returned):"
    $Height = Log "Height:" 3 ? "Inch"
    $Width  = Log "Width:"  4 ? "Inch"
    Log-Verbose "Or one display/log line spread over multiple code lines:"
    Log "Periphery:" -NoNewline
    $Periphery = Log (2 * $Height + 2 * $Width) ? -Color Green -NoNewline
    Log "Inch"
    Log-Debug "Password:" $Password "(This will not be shown and captured unless the common -Debug argument is supplied)"
    

    <强>显示

    示例命令以下列格式显示:

    Display

    日志文件

    示例命令在日志文件中记录以下信息:

    2017-07-13  PowerShell version: 5.1.15063.483, process start: 2017-07-13 15:39:44
    15:39:46.75 Log-Entry version: 02.00.01, command line: C:\Users\User\Scripts\Log-Entry\Log-Entry.ps1 
    15:39:46.80 Examples:
    15:39:46.94 Several examples that usually aren't displayed by Write-Host: $Null @() @() @(@(), @()) @($Null)
    15:39:46.95         Note 1: An empty string: isn't displayed by Log-Entry either (as you do not want every comment quoted).
    15:39:46.98                 In case you want to reveal a (possible) empty string, use -QuoteString: ""
    15:39:47.00         Note 2: An empty array embedded in another array: @() is flattened by PowerShell (and not Write-Log).
    15:39:47.01                 To prevent this use a comma in front of the embbed array:  @(@())
    15:39:47.05 A hashtable: @{one = 1, three = 3, two = 2}
    15:39:47.06 A recursive hashtable: @{
                    one = @{
                        one = @{
                            one = 1, 
                            three = 3, 
                            two = 2
                        }, 
                        three = 3, 
                        two = 2
                    }, 
                    three = 3, 
                    two = 2
                }
    15:39:47.10 Character array: @(H, a, l, l, o,  , W, o, r, l, d)
    15:39:47.11 The following line produces a error which is captured in the log file:
    Error at 51,23: Cannot find path 'C:\NoSuchFile.txt' because it does not exist.
    15:39:47.15 File: $Null
    15:39:47.16 The switch -FlushErrors prevents the error being logged:
    15:39:47.17 File: $Null
    15:39:47.17 Below are two inline log examples (the object preceding the "?" is returned):
    15:39:47.18 Height: 3 Inch
    15:39:47.19 Width: 4 Inch
    15:39:47.19 Or one display/log line spread over multiple code lines:
    15:39:47.20 Periphery: 14 Inch
    15:39:47.27 End (Execution time: 00:00:00.5781145, Process time: 00:00:03.1067112)
    

答案 1 :(得分:1)

您可以使用Write-Log功能。在我相信的情况下,这将是最好的方式。

这个功能由Jason Wasser撰写:

<#
.Synopsis
   Write-Log writes a message to a specified log file with the current time stamp.
.DESCRIPTION
   The Write-Log function is designed to add logging capability to other scripts.
   In addition to writing output and/or verbose you can write to a log file for
   later debugging.
.NOTES
   Created by: Jason Wasser @wasserja
   Modified: 11/24/2015 09:30:19 AM  

   Changelog:
    * Code simplification and clarification - thanks to @juneb_get_help
    * Added documentation.
    * Renamed LogPath parameter to Path to keep it standard - thanks to @JeffHicks
    * Revised the Force switch to work as it should - thanks to @JeffHicks

   To Do:
    * Add error handling if trying to create a log file in a inaccessible location.
    * Add ability to write $Message to $Verbose or $Error pipelines to eliminate
      duplicates.
.PARAMETER Message
   Message is the content that you wish to add to the log file. 
.PARAMETER Path
   The path to the log file to which you would like to write. By default the function will 
   create the path and file if it does not exist. 
.PARAMETER Level
   Specify the criticality of the log information being written to the log (i.e. Error, Warning, Informational)
.PARAMETER NoClobber
   Use NoClobber if you do not wish to overwrite an existing file.
.EXAMPLE
   Write-Log -Message 'Log message' 
   Writes the message to c:\Logs\PowerShellLog.log.
.EXAMPLE
   Write-Log -Message 'Restarting Server.' -Path c:\Logs\Scriptoutput.log
   Writes the content to the specified log file and creates the path and file specified. 
.EXAMPLE
   Write-Log -Message 'Folder does not exist.' -Path c:\Logs\Script.log -Level Error
   Writes the message to the specified log file as an error message, and writes the message to the error pipeline.
.LINK
   https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0
#>
function Write-Log
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [Alias("LogContent")]
        [string]$Message,

        [Parameter(Mandatory=$false)]
        [Alias('LogPath')]
        [string]$Path='C:\Logs\PowerShellLog.log',

        [Parameter(Mandatory=$false)]
        [ValidateSet("Error","Warn","Info")]
        [string]$Level="Info",

        [Parameter(Mandatory=$false)]
        [switch]$NoClobber
    )

    Begin
    {
        # Set VerbosePreference to Continue so that verbose messages are displayed.
        $VerbosePreference = 'Continue'
    }
    Process
    {

        # If the file already exists and NoClobber was specified, do not write to the log.
        if ((Test-Path $Path) -AND $NoClobber) {
            Write-Error "Log file $Path already exists, and you specified NoClobber. Either delete the file or specify a different name."
            Return
            }

        # If attempting to write to a log file in a folder/path that doesn't exist create the file including the path.
        elseif (!(Test-Path $Path)) {
            Write-Verbose "Creating $Path."
            $NewLogFile = New-Item $Path -Force -ItemType File
            }

        else {
            # Nothing to see here yet.
            }

        # Format Date for our Log File
        $FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"

        # Write message to error, warning, or verbose pipeline and specify $LevelText
        switch ($Level) {
            'Error' {
                Write-Error $Message
                $LevelText = 'ERROR:'
                }
            'Warn' {
                Write-Warning $Message
                $LevelText = 'WARNING:'
                }
            'Info' {
                Write-Verbose $Message
                $LevelText = 'INFO:'
                }
            }

        # Write log entry to $Path
        "$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append
    }
    End
    {
    }
}

<强>用法:

 Write-Log -Message 'Folder does not exist.' -Path c:\Logs\Script.log -Level Error

Write-Log -Message 'Restarting Server.' -Path c:\Logs\Scriptoutput.log

注意:您可以始终对此功能使用get-help来获取所有详细信息。

希望它有所帮助。