Powershell ScriptBlock 关闭:我错过了什么吗?

问题描述

我已经为此苦苦挣扎了几个小时,在阅读了许多关于 ScriptBlocks、Closures、Scopes 等的帖子后,我仍然没有看到我的代码有什么问题。

让我解释一下:我有一个使用 PSWriteHTML 模块和 ScriptBlocks 动态生成 HTML 页面的主脚本。

由于我有很多 PSWriteHTML 页面要编写,我使用一个 ScriptBlocks 的 arrayList 来生成每次具有不同值集的代码(对应于不同的服务器),这些 ScriptBlocks 被执行到一个 foreach 循环。

这是使用Save-utilizationReport函数完成的(我只保留了相关代码):

function Save-utilizationReport ($currentDate,$navLinksScriptBlock,$htmlScriptBlockArray,$emeaTotalNumberOfCalls,$namTotalNumberOfCalls,$apacTotalNumberOfCalls,$path,$logFilePath) {
    
    [...]

    # Using Script Blocks,add the pages generated during the analysis to the HTML Report
    foreach($htmlScriptBlock in $htmlScriptBlockArray){
        Invoke-Command -ScriptBlock ($htmlScriptBlock)
    }

    [...]
}

ScriptBlock 是使用从服务器日志列表中收集的一组值创建的,并添加Create-utilizationReportPage 函数中的 ScriptBlocks arrayList(同样,我只保留了相关代码):

function Create-utilizationReportPage ($matchedLines,$ipAddress,$hostname,$pageId,$utilisationReportTemplate,$htmlScriptBlockArray) {

# Retrieve the content of the Utilisation Report Template as a RAW string (Here-String)
$htmlPageCodeBlock = Get-Content $utilisationReportTemplate -Raw

# Create the Script Block that contains the HTML page
$htmlPageScriptBlock = {
    
    # Get the "Total number of calls" information
    $timeArray = $matchedLines.time
    $participantNumberArray = $matchedLines.participantNumber

    [...]
    
    # Update the page ID in the template
    $htmlPageCodeBlock = $htmlPageCodeBlock -replace '%PAGE_ID%',"$pageId"
    
    # Update the page header information in the template
    $htmlPageCodeBlock = $htmlPageCodeBlock -replace '%PAGE_HEADER%',"$ipAddress [$hostname]"
    
    Invoke-Command -ScriptBlock ([scriptblock]::Create($htmlPageCodeBlock))
}.GetNewClosure()

# Add the Page's script block to the Script Blocks array
$htmlScriptBlockArray.Add($htmlPageScriptBlock)
}

这些在下面的脚本中被调用

$currentDate = Get-CorrectDate $latestFolder

# The global Log file
$logFilePath = "$scriptPath/Logs/logs_$currentDate.txt"

$serversList = "$scriptPath/Config/$configFileName"

# If the Servers list exists,retrieve the Servers list
if (Test-Path $serversList) {
    # Get the data from the file
    [xml]$servers = Get-Content $serversList

    # Select only the Servers information
    $nodes = $servers.SelectNodes("//server")
        
    try {
    
        [...]
    
        # Iterate through the Servers list
        foreach ($node in $nodes) {
        
            # Get the Server IP Address
            $ipAddress = $node.ip
            
            # Get the Server Hostname
            $hostname = $node.hostname
            
            # Get the "Debug utilization" lines from the logbundle's syslog files
            $matchedLines = [System.Collections.ArrayList]@(Find-utilizationLinesInLogs $currentDate "$scriptPath\Data\$currentDate\logs_$ipAddress" "host:server:  \[USAGE\] : \{`"1`" : ")[-1]

            # Add the Server's participants throughout the day
            switch ($hostname) {
                {$_.Contains("emea")} {
                    # Update EMEA total number of participants
                    Update-ZonetotalParticipants ([ref]$emeaServer) ([ref]$emeaTotalNumberOfCalls) $matchedLines
                }
                {$_.Contains("nam")} {
                    # Update NAM total number of participants
                    Update-ZonetotalParticipants ([ref]$namServer) ([ref]$namTotalNumberOfCalls) $matchedLines
                }
                {$_.Contains("apac")} {
                    # Update APAC total number of participants
                    Update-ZonetotalParticipants ([ref]$apacServer) ([ref]$apacTotalNumberOfCalls) $matchedLines
                }
            }
            
            # Get the cpu utilization values lines from the logbundle's sysdebug files
            $cpu = Get-cpuUsage "$scriptPath\Data\$currentDate\logs_$ipAddress\sysdebug"

            # Get the Memory utilization values lines from the logs' sysdebug files
            $memory = Get-MemoryUsage "$scriptPath\Data\$currentDate\logs_$ipAddress\sysdebug"

            # Export the "Debug utilization" to a CSV file
            Save-csvutilizationReport $matchedLines "$scriptPath\Output\$currentDate" "$ipAddress" "$hostname" $logFilePath

            # Draw graphs from the "Debug utilization" information and then export it to an HTML File
            Create-utilizationReportPage $matchedLines "$ipAddress" "$hostname" $pageId $utilisationReportTemplate $htmlScriptBlockArray
            
            # Add the new navigation link to the Navigation Links array
            Add-htmlNavLink $navLinksArray "$ipAddress" "$hostname" $pageId $logFilePath
            
            # Increment the Page ID counter
            $pageId += 1
        }
    
        # Create an Here-String from the Navigation Links array
        $OFS = ""
        $navLinksCode =@"
        $($navLinksArray)
"@
        $OFS = " "

        # Create a script block from the Navigation Links
        $navLinksScriptBlock = [scriptblock]::Create($navLinksCode)

        # Save the daily HTML utilization report
        Save-utilizationReport $currentDate $navLinksScriptBlock $htmlScriptBlockArray $emeaTotalNumberOfCalls $namTotalNumberOfCalls $apacTotalNumberOfCalls "$scriptPath\Output\$currentDate" $logFilePath
    }
    catch
    {
        Write-Logs $logFilePath "Error: $($_.Exception.Message)"
        
        exit 1
    }
}

除了第一页中的第一组值是所有其他值集的总和外,一切正常,并且如预期的一样......

例如当我有 3 个页面时,执行 Find-utilizationLinesInLogs 函数时可以看到收集的值是正确的:

  1. ma​​tchedLines.participantNumber: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 5 5 5 4 4 4 4 4 14 1 1 16 16 16 7 7 7 7 8 14 17 18 19 18 19 19 20 16 16 16 15 7 7 7 5 4 4 4 4 4 4 4 1 1 0 0 0 3 5 1 6 5 5 5 >

  2. ma​​tchedLines.participantNumber: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 8 1 0 9 10 10 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 9 4 12 15 11 14 14 13 12 12 12 1 1 1 1 1 1 1 1 1 1 1 0 1 0 1 >

  3. ma​​tchedLines.participantNumber: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 0 0 0 3 9 9 10 8 10 9 9 10 10 10 10 10 11 11 11 11 8 7 8 8 7 7 7 7 7 7 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 6 1 6 1 6 1 6 >

但是当在 foreach 循环中使用 Invoke-Command 执行 ScriptBlocks 时,第一批值系统地是 3 组值的总和,而以下是正确的:

  1. Create-utilizationReportPage 内的matchedLines.participantNumber0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 6 6 5 7 7 5 8 29 33 34 34 34 36 34 25 26 26 26 27 32 36 37 38 37 35 34 36 32 31 32 26 26 29 25 22 20 18 1 6 7 2 6 7 2 7 7 25 31 32 34

  2. Create-utilizationReportPage 内的matchedLines.participantNumber: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 8 1 0 9 10 10 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 9 4 12 15 11 14 14 13 12 12 12 1 1 1 1 1 1 1 1 1 1 1 0 1 0 1 >

  3. Create-utilizationReportPage 内的matchedLines.participantNumber: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 0 0 0 3 9 9 10 8 10 9 9 10 10 10 10 10 11 11 11 11 8 7 8 8 7 7 7 7 7 7 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 6 1 6 1 6 1 6 >

我尝试了很多事情都没有成功,所以如果有人对可能出错的地方有任何暗示,那就太好了!

感谢您的帮助!

解决方法

所以!我终于知道出了什么问题。

我怀疑我的问题与 arrayList 副本或至少是可变副本有关...所以我试图删除脚本的这一部分,在那里我提取了 $macthedLines 值并使用引用将它们复制到全局数组中:

            # Add the Server's participants throughout the day
            switch ($hostname) {
                {$_.Contains("emea")} {
                    # Update EMEA total number of participants
                    Update-ZoneTotalParticipants ([ref]$emeaServer) ([ref]$emeaTotalNumberOfCalls) $matchedLines
                }
                {$_.Contains("nam")} {
                    # Update NAM total number of participants
                    Update-ZoneTotalParticipants ([ref]$namServer) ([ref]$namTotalNumberOfCalls) $matchedLines
                }
                {$_.Contains("apac")} {
                    # Update APAC total number of participants
                    Update-ZoneTotalParticipants ([ref]$apacServer) ([ref]$apacTotalNumberOfCalls) $matchedLines
                }
            }

还有宾果游戏,这次写在第一个 PSWriteHTML 页面中的值是正确的!

所以我专注于执行值复制的 Update-ZoneTotalParticipants 函数:

function Update-ZoneTotalParticipants ([ref][int]$ServerNumber,[ref][System.Collections.ArrayList]$totalNumberOfCalls,$values) {
    # If this is the first server to be analysed in the zone
    if ($ServerNumber.value -eq 1) {

        # Copy the Utilization lines of the server
        $totalNumberOfCalls.value = $values
        
        # Increment the zone's server counter
        $ServerNumber.value += 1
    }
    # If this at least the 2nd server to be analysed in the zone
    elseif ($ServerNumber.value -gt 1) {

        # Parse the server matched lines,get the participantNumber value and add it to the total
        0..($totalNumberOfCalls.value.Count - 1) | ForEach-Object {
            if ($_ -le ($values.Count - 1)) {
                $totalNumberOfCalls.value[$_].participantNumber = [int]($totalNumberOfCalls.value[$_].participantNumber) + [int]($values[$_].participantNumber)
            }
            # If there are less objects in the current server matched lines,add a 0 instead
            else {
                $totalNumberOfCalls.value[$_].participantNumber = [int]($totalNumberOfCalls.value[$_].participantNumber) + 0
            }
        }
    }
}

涉及$matchedLines 的唯一代码部分是$totalNumberOfCalls.value = $values,所以肯定是数组操作出错了。

因此,我仔细研究了 ArrayList 副本或克隆,发现我没有对对象进行深层复制,这可能会导致问题。

我使用了Petru Zaharia的解决方案in this thread来更新函数:

        # Copy the Utilization lines of the server
        $totalNumberOfCalls.value = $values

# replaced with:

        # Copy the Utilization lines of the server : Serialize and Deserialize data using PSSerializer:
        $_TempCliXMLString = [System.Management.Automation.PSSerializer]::Serialize($matchedLines,[int32]::MaxValue)
        $totalNumberOfCalls.value = [System.Management.Automation.PSSerializer]::Deserialize($_TempCliXMLString)

现在一切都按预期进行。

感谢大家的支持!

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...