Powershell-遍历数组

时间:2019-04-22 16:23:46

标签: powershell

我正在搜索以下站点中所有Windows服务器的C和E驱动器 包含putty.exe及其版本的任何现有副本的Active Directory。  输出需要具有服务器名称,可执行文件的完整路径, 和文件版本。到目前为止,我有以下代码(目前仅在使用 两台服务器进行测试:

$ComputerName = Get-ADComputer -filter "name -like 'computer01' -or name `
-like 'server01'" | select -ExpandProperty name

$OutputArr = @()

$findFiles = foreach($computer in $computername){

    $file = Invoke-Command -computername $computer { Get-ChildItem -Path `
    c:\, e:\ -Recurse | where-object{(!$_.psiscontainer -eq $true) -and `
    ($_.name -like "putty.exe")} | ForEach-Object -process {$_.fullname} }


    $output = $OutputObj = New-Object -TypeName PSobject  
    $OutputObj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $computer
    $OutputObj | Add-Member -MemberType NoteProperty -Name FilePath -Value $file

    $OutputArr += $OutputObj
    Write-Verbose $OutputObj
}

$OutputArr | fl

上面的代码输出以下数组:

ComputerName : COMPUTER01
FilePath     : {C:\Program Files\PuTTY\putty.exe, C:\Program Files (x86)\PuTTY\PUTTY.EXE}

ComputerName : SERVER01
FilePath     : {C:\Program Files (x86)\putty\putty.exe, C:\Users\testuser\Desktop\Public Desktop\putty.exe}

这将产生正确的数据,但是现在我需要针对每个运行另一个代码段 计算机名下的单独文件路径,但是不确定如何完成此操作 带有多个条目的完整文件路径。

基本上,我需要将数组中的每个ComputerName分成多行:

COMPUTER01,C:\Program Files\PuTTY\putty.exe
COMPUTER01,C:\Program Files (x86)\PuTTY\PUTTY.EXE
SERVER01,C:\Program Files (x86)\putty\putty.exe

等等...

数组不是正确的方法吗?

3 个答案:

答案 0 :(得分:0)

我不确定您到底想做什么,但这应该可以遍历您的自定义对象。您的Invoke-Command也可以简化。

$file = Invoke-Command -computername $computer { Get-ChildItem -Path "C:\", "E:\" -Recurse -File -Filter "putty.exe" | Select -Property VersionInfo }

$OutputObj = New-Object -TypeName PSobject  
$OutputObj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $env:COMPUTERNAME
$OutputObj | Add-Member -MemberType NoteProperty -Name FilePath -Value $file

$OutputArr += $OutputObj

foreach ($item in $OutputArr)
{
    for ($i = 0; $i -lt $item.FilePath.Count; $i++)
    { 
         Write-Output ([string]::Join(', ', $item.ComputerName, $item.FilePath[$i].VersionInfo.FileName, $item.FilePath[$i].VersionInfo.FileVersion))
    }       
}

答案 1 :(得分:0)

进行远程会话时,PowerShell可能会变得棘手。以下脚本对您来说应该是一个很好的起点。以下是一些其他需要改进的地方:

  1. 在驱动器的根目录执行Get-ChildItem -Recurse会占用过多的内存,并且由于100%的内存使用,您可能会导致意外的页面文件扩展,甚至使服务器无响应。在下面的代码段中,我使用了一系列众所周知的路径。如果您需要确定是否在其他计算机上启动了putty.exe,则您的监视解决方案有望获得过程性能数据,您可以在其中搜索putty.exe。
  2. 说到内存管理,远程外壳对它们可以使用多少内存有限制。如果运行winrm get winrm/config/winrs,则会看到上限。
  3. 如果要从远程脚本块中对其他资源进行身份验证,则需要设置支持双跳方案(CredSSP或Kerberos)的身份验证
$computerNames = @('computer1','computer2')

foreach($computer in $computerNames)
{
    <# 
        First Script Block checks well known paths for putty.exe
    #>
    $puttyResults = Invoke-Command -ComputerName $computer -ScriptBlock {

        $wellKnownPaths = @()
        $wellKnownPaths += Join-Path $env:USERPROFILE -ChildPath "Desktop"
        $wellKnownPaths += "D:\tools\"
        $wellKnownPaths += $env:Path.Split(';')

        $puttyPaths = @()
        foreach($path in $wellKnownPaths)
        {
            $puttyPaths += Get-ChildItem $path -Filter "putty.exe" -Recurse
        }

        if($puttyPaths.Count -gt 0)
        {
            $resultsArray = @()
            foreach($path in $puttyPaths)
            {
                $resultsArray += [PSCustomObject]@{
                    ComputerName = $env:COMPUTERNAME
                    PuttyPath = $path.FullName
                }
            }

            return $resultsArray
        }

        return $null
    }

    if($puttyResults -ne $null)
    {
        foreach($result in $puttyResults)
        {
            <#
                Second script block takes action against putty.exe
            #>
            $puttyExists = Invoke-Command -ComputerName $computer -ArgumentList @($result.PuttyPath) -ScriptBlock {
                Param(
                    $PuttyPath
                )

                return (Test-Path $PuttyPath)
            }

            if($puttyExists)
            {
                $msg = "Putty exists on '{0}', at '{1}'" -f $result.ComputerName, $result.PuttyPath
                Write-Host $msg -ForegroundColor:Yellow
            }
        }        
    }
}

答案 2 :(得分:0)

如果严格使用$OutputArr中已存储的内容,则可以进行以下操作:

$out = foreach ($line in $OutputArr) {
   if ($line.filepath.count -gt 1) {
     foreach ($fp in $line.FilePath) {
       [pscustomobject][ordered]@{ComputerName = $line.ComputerName; FilePath = $fp}
     }
   } 
   else {
     $line
   }
 } 

$out | ConvertTo-Csv -NoTypeInformation

foreach循环使用属性ComputerName和FilePath创建新对象,并将它们作为对象数组存储在$out中。

如果您不关心属性,而只希望使用逗号分隔的列表,则可以使用以下内容:

foreach ($line in $OutputArr) {
  if ($line.filepath.count -gt 1) {
    foreach ($fp in $line.FilePath) {
      "{0},{1}" -f $line.ComputerName,$fp 
    }
  }
  else {
      "{0},{1}" -f $line.ComputerName,$line.FilePath
  }
 } 

这与第一个解决方案执行相同的循环,但是使用格式运算符(-f)格式化输出。传递到ConvertTo-Csv时,会将输出格式为逗号分隔,并带有标题属性。

您甚至可以在将任何内容存储在$OutputArr中之前将所需的功能移入代码中。我觉得在创建$OutputArr的所有其他循环之后进行所有这些操作只会增加效率低下。