Powershell错误处理未按预期使用函数

Powershell错误处理未按预期使用函数,powershell,Powershell,正在寻找有关Powershell中错误处理的建议。我想我理解使用Try/Catch背后的概念,但是我正在努力在我的脚本中使用它,或者我需要的粒度如何 例如,我应该在函数中使用try/catch吗?如果是,我应该在try中插入函数的操作还是需要中断它 再往下走?或者,我应该在调用函数时尝试处理错误吗?这样做: Try{ Get-MyFunction } catch{ Do Something" } Write-Host "[output]" Write-Host "res

正在寻找有关Powershell中错误处理的建议。我想我理解使用Try/Catch背后的概念,但是我正在努力在我的脚本中使用它,或者我需要的粒度如何

例如,我应该在函数中使用try/catch吗?如果是,我应该在try中插入函数的操作还是需要中断它

再往下走?或者,我应该在调用函数时尝试处理错误吗?这样做:

Try{
     Get-MyFunction 
    } catch{ Do Something" 
} 
Write-Host "[output]"
Write-Host "result=0"
Write-Host "msg = $VariableContainingOutput -NoNewline
下面是我编写的一个脚本的示例,该脚本正在检查设备上的一些危害指标。我有一个应用程序将启动此脚本并捕获最终输出。应用程序要求最终输出采用以下格式,因此任何故障都会生成此格式

[output]
result=<0 or 1>
msg= <string>
我的两个函数创建自定义对象,然后将它们合并为最终输出,因此我希望以相同的格式捕获任何错误。如果一个函数生成错误,它应该记录这些错误并继续

如果我只是自己运行代码,而不使用函数,那么这是可行的,但使用函数时,不会捕获错误

这需要在PowerShell 2及更高版本上运行。未显示此脚本调用的Add RegMember和Get RegValue函数

function Get-ChangedRunKey {
    [CmdletBinding()]
    param()
    process 
    {
        $days = '-365' 
        $Run = @()
        $AutoRunOutput = @()
        $RunKeyValues = @("HKLM:\Software\Microsoft\Windows\CurrentVersion\Run", 
                      "HKLM:\Software\Wow6432node\Microsoft\Windows\CurrentVersion\Run", 
                      "HKU:\S-1-5-21-*\Software\Microsoft\Windows\CurrentVersion\Run",
                      "HKU:\S-1-5-21-*\Software\Wow6432node\Microsoft\Windows\CurrentVersion\Run"
                      )
        Try{
            $Run += $RunKeyValues | 
            ForEach-Object {
                Get-Item $_ -ErrorAction SilentlyContinue | 
                Add-RegKeyMember -ErrorAction SilentlyContinue | 
                Where-Object {
                    $_.lastwritetime -gt (Get-Date).AddDays($days)
                } | 
                Select-Object Name,LastWriteTime,property
            } 
            if ($Run -ne $Null)
            {
                $AutoRunPath = ( $Run | 
                    ForEach-Object {
                        $_.name
                    }
                ) -replace "HKEY_LOCAL_MACHINE", "HKLM:" -replace "HKEY_Users", "HKU:"
                $AutoRunValue = $AutoRunPath  | 
                    Where-Object { 
                        $_ -and $_.Trim() 
                    } |
                    ForEach-Object {
                        Get-RegValue -path $_ -Name '*' -ErrorAction SilentlyContinue
                    }
            }
        #Build Custom Object if modified Run keys are found 
            if($AutorunValue -ne $null)
            {
                foreach ($Value in $AutoRunValue) {
                    $AutoRunOutput += New-Object PSObject -Property @{
                        Description = "Autorun"
                        path = $Value.path
                        value = $Value.value
                    }
                }
            }
            Write-Output $AutoRunOutput
        }catch{
                $AutoRunOutput += New-Object PSObject -Property @{
                    Description = "Autorun"
                    path = "N/A"
                    value = "Error accessing Autorun data. $($Error[0])"
                }
        }
    }
}
function Get-ShellIOC {
    [CmdletBinding()]
    param()
    process 
    {
        $ShellIOCOutput = @()
        $ShellIOCPath = 'HKU:\' + '*' + '_Classes\*\shell\open\command'
     Try{
            $ShellIOCValue = (Get-Item $ShellIOCPath -ErrorAction SilentlyContinue | 
                Select-Object name,property | 
                ForEach-Object {
                    $_.name
                }
            ) -replace "HKEY_LOCAL_MACHINE", "HKLM:" -replace "HKEY_Users", "HKU:" 
            $ShellIOCDetected = $ShellIOCValue | 
                ForEach-Object {
                    Get-RegValue -path $_ -Name '*' -ErrorAction SilentlyContinue
                } | 
                Where-Object {
                    $_.value -like "*cmd.exe*" -or 
                    $_.value -like "*mshta.exe*"
                }
            if($ShellIOCDetected -ne $null) 
            { 
                foreach ($ShellIOC in $ShellIOCDetected) {
                    $ShellIOCOutput += New-Object PSObject -Property @{
                        Description = "Shell_IOC_Detected"
                        path = $ShellIOC.path
                        value = $ShellIOC.value
                    }
                }
            }
            Write-Output $ShellIOCOutput
        }catch{
                $ShellIOCOutput += New-Object PSObject -Property @{
                    Description = "Shell_IOC_Detected"
                    path = "N/A"
                    value = "Error accessing ShellIOC data. $($Error[0])"
                    }
        }
    }
}
function Set-OutputFormat {
    [CmdletBinding()]
    param()
    process 
    {   
        $FormattedOutput = $AutoRunOutput + $ShellIOCOutput | 
        ForEach-Object {
            "Description:" + $_.description + ',' + "Path:" + $_.path + ',' + "Value:" + $_.value + "|"
        }
        Write-Output $FormattedOutput 
    }
}

if (!(Test-Path "HKU:\")){
    try{
        New-PSDrive -PSProvider Registry -Root HKEY_USERS -Name HKU -ErrorAction Stop | Out-Null
    }catch{
        Write-Output "[output]"
        Write-Output "result=0"
        Write-Host "msg = Unable to Connect HKU drive" -NoNewline
    }
}  
$AutoRunOutput = Get-ChangedRunKey

$ShellIOCOutput = Get-ShellIOC 

$FormattedOutput = Set-OutputFormat 

Write-Output "[output]"

if ($FormattedOutput -eq $Null)
{
    Write-Output "result=0"
    Write-Host "msg= No Items Detected" -NoNewline
}
else
{
    Write-Output "result=1"
    Write-Host "msg=Items Detected: $($FormattedOutput)" -NoNewline
}

您必须知道PowerShell中有两种错误类型:

终止错误:在catch块中自动捕获这些错误

非终止错误:如果要捕获它们,则需要使用-ErrorAction Stop执行相关命令。如果它不是PowerShell命令,而是可执行文件,则需要检查退出代码或$?。因此,我建议将整个操作包装在一个高级函数中,然后使用-ErrorAction Stop调用该函数


除此之外,我想指出,PowerShell版本2已经被弃用。存在非终止错误的原因是,在某些情况下,例如处理管道中的多个对象时,您可能不希望仅因为它对一个对象不起作用而停止。请不要使用写主机、使用写详细或写输出,具体取决于用例。

首先,如果在函数中使用写主机,则在调用函数并将输出分配给变量时,将永远不会捕获该输出。如果需要可以捕获的输出,请使用写输出。其次,这个问题中与何时/何地/如何实现错误处理有关的部分属于主要基于意见的类别。好的,让我再看一看。我使用写主机是因为写输出没有-nonewline参数。此应用程序不允许在输出中出现换行,这是在我导出最终组合的自定义对象时发生的。不过,我可能会从函数中删除它。至于你的另一点,我不是在寻找正确/错误的方法,而是最佳实践。我读过的所有内容都只涉及到做什么的文档,而不是什么时候做。什么时候做是意见。另外,你把函数的输出赋值给一个变量。在catch代码中,您从未实际输出任何内容。因此,不会为变量赋值。好的,我现在看到了。让我更新一下。所以在我之前的评论中,我错了。我想我在函数中有一个写主机,但它不是。嗯,我一直在使用写主机。任何只是信息性的输出,我不想捕获。我基本上使用它来确保长时间运行的进程不会挂起。这比进度条简单多了。但是其他人可能不想在主机上看到输出。这就是写主机的主要问题,也是为什么应该使用Write Verbose,因为详细流可以重定向。从马口中说:啊……我完全支持“请不要使用Write Host,在编写您将与他人共享的代码时,请使用“编写详细信息”或“编写输出”。“我在清理代码以供他人使用时所做的各种事情都不同于现在为我快速完成某些任务时所做的事情。同样来自那篇文章:我在编写一次性脚本或函数时经常使用“编写主机”。