【问题标题】:PowerShell - Export-Csv : Cannot bind argument to parameter 'InputObject' because it is nullPowerShell - Export-Csv:无法将参数绑定到参数“InputObject”,因为它为空
【发布时间】:2021-04-19 20:31:18
【问题描述】:

我正在使用我在网上找到的 PowerShell 函数,效果很好。当合并到我的脚本中以针对给定域中的 Windows 服务器使用它时,我将输出导出到 .csv。它确实有效,但是在脚本运行的整个过程中,我收到有关“InputObject”的错误,我只是想清理它,但我没有看到问题出在哪里。任何帮助表示赞赏。下面是我使用的函数,然后是我使用该函数的脚本,然后是我得到的错误。

函数

Function Get-LocalGroupMembership {
    <#
        .SYNOPSIS
            Recursively list all members of a specified Local group.

        .DESCRIPTION
            Recursively list all members of a specified Local group. This can be run against a local or
            remote system or systems. Recursion is unlimited unless specified by the -Depth parameter.

            Alias: glgm

        .PARAMETER Computername
            Local or remote computer/s to perform the query against.
            
            Default value is the local system.

        .PARAMETER Group
            Name of the group to query on a system for all members.
            
            Default value is 'Administrators'

        .PARAMETER Depth
            Limit the recursive depth of a query. 
            
            Default value is 2147483647.

        .PARAMETER Throttle
            Number of concurrently running jobs to run at a time

            Default value is 10

        
    #>
    [cmdletbinding()]
    Param (
        [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [Alias('CN','__Server','Computer','IPAddress')]
        [string[]]$Computername = $env:COMPUTERNAME,
        [parameter()]
        [string]$Group = "Administrators",
        [parameter()]
        [int]$Depth = ([int]::MaxValue),
        [parameter()]
        [Alias("MaxJobs")]
        [int]$Throttle = 10
    )
    Begin {
        $PSBoundParameters.GetEnumerator() | ForEach {
            Write-Verbose $_
        }
        #region Extra Configurations
        Write-Verbose ("Depth: {0}" -f $Depth)
        #endregion Extra Configurations
        #Define hash table for Get-RunspaceData function
        $runspacehash = @{}
        #Function to perform runspace job cleanup
        Function Get-RunspaceData {
            [cmdletbinding()]
            param(
                [switch]$Wait
            )
            Do {
                $more = $false         
                Foreach($runspace in $runspaces) {
                    If ($runspace.Runspace.isCompleted) {
                        $runspace.powershell.EndInvoke($runspace.Runspace)
                        $runspace.powershell.dispose()
                        $runspace.Runspace = $null
                        $runspace.powershell = $null                 
                    } ElseIf ($runspace.Runspace -ne $null) {
                        $more = $true
                    }
                }
                If ($more -AND $PSBoundParameters['Wait']) {
                    Start-Sleep -Milliseconds 100
                }   
                #Clean out unused runspace jobs
                $temphash = $runspaces.clone()
                $temphash | Where {
                    $_.runspace -eq $Null
                } | ForEach {
                    Write-Verbose ("Removing {0}" -f $_.computer)
                    $Runspaces.remove($_)
                }             
            } while ($more -AND $PSBoundParameters['Wait'])
        }

        #region ScriptBlock
            $scriptBlock = {
            Param ($Computer,$Group,$Depth,$NetBIOSDomain,$ObjNT,$Translate)            
            $Script:Depth = $Depth
            $Script:ObjNT = $ObjNT
            $Script:Translate = $Translate
            $Script:NetBIOSDomain = $NetBIOSDomain
            Function Get-LocalGroupMember {
                [cmdletbinding()]
                Param (
                    [parameter()]
                    [System.DirectoryServices.DirectoryEntry]$LocalGroup
                )
                # Invoke the Members method and convert to an array of member objects.
                $Members= @($LocalGroup.psbase.Invoke("Members"))
                $Counter++
                ForEach ($Member In $Members) {                
                    Try {
                        $Name = $Member.GetType().InvokeMember("Name", 'GetProperty', $Null, $Member, $Null)
                        $Path = $Member.GetType().InvokeMember("ADsPath", 'GetProperty', $Null, $Member, $Null)
                        # Check if this member is a group.
                        $isGroup = ($Member.GetType().InvokeMember("Class", 'GetProperty', $Null, $Member, $Null) -eq "group")
                        If (($Path -like "*/$Computer/*")) {
                            $Type = 'Local'
                        } Else {$Type = 'Domain'}
                        New-Object PSObject -Property @{
                            Computername = $Computer
                            Name = $Name
                            Type = $Type
                            ParentGroup = $LocalGroup.Name[0]
                            isGroup = $isGroup
                            Depth = $Counter
                        }
                        If ($isGroup) {
                            # Check if this group is local or domain.
                            #$host.ui.WriteVerboseLine("(RS)Checking if Counter: {0} is less than Depth: {1}" -f $Counter, $Depth)
                            If ($Counter -lt $Depth) {
                                If ($Type -eq 'Local') {
                                    If ($Groups[$Name] -notcontains 'Local') {
                                        $host.ui.WriteVerboseLine(("{0}: Getting local group members" -f $Name))
                                        $Groups[$Name] += ,'Local'
                                        # Enumerate members of local group.
                                        Get-LocalGroupMember $Member
                                    }
                                } Else {
                                    If ($Groups[$Name] -notcontains 'Domain') {
                                        $host.ui.WriteVerboseLine(("{0}: Getting domain group members" -f $Name))
                                        $Groups[$Name] += ,'Domain'
                                        # Enumerate members of domain group.
                                        Get-DomainGroupMember $Member $Name $True
                                    }
                                }
                            }
                        }
                    } Catch {
                        $host.ui.WriteWarningLine(("GLGM{0}" -f $_.Exception.Message))
                    }
                }
            }

            Function Get-DomainGroupMember {
                [cmdletbinding()]
                Param (
                    [parameter()]
                    $DomainGroup, 
                    [parameter()]
                    [string]$NTName, 
                    [parameter()]
                    [string]$blnNT
                )
                Try {
                    If ($blnNT -eq $True) {
                        # Convert NetBIOS domain name of group to Distinguished Name.
                        $objNT.InvokeMember("Set", "InvokeMethod", $Null, $Translate, (3, ("{0}{1}" -f $NetBIOSDomain.Trim(),$NTName)))
                        $DN = $objNT.InvokeMember("Get", "InvokeMethod", $Null, $Translate, 1)
                        $ADGroup = [ADSI]"LDAP://$DN"
                    } Else {
                        $DN = $DomainGroup.distinguishedName
                        $ADGroup = $DomainGroup
                    }         
                    $Counter++   
                    ForEach ($MemberDN In $ADGroup.Member) {
                        $MemberGroup = [ADSI]("LDAP://{0}" -f ($MemberDN -replace '/','\/'))
                        New-Object PSObject -Property @{
                            Computername = $Computer
                            Name = $MemberGroup.SamAccountName[0]
                            Type = 'Domain'
                            ParentGroup = $NTName
                            isGroup = ($MemberGroup.Class -eq "group")
                            Depth = $Counter
                        }
                        # Check if this member is a group.
                        If ($MemberGroup.Class -eq "group") {              
                            If ($Counter -lt $Depth) {
                                If ($Groups[$MemberGroup.name[0]] -notcontains 'Domain') {
                                    Write-Verbose ("{0}: Getting domain group members" -f $MemberGroup.name[0])
                                    $Groups[$MemberGroup.name[0]] += ,'Domain'
                                    # Enumerate members of domain group.
                                    Get-DomainGroupMember $MemberGroup $MemberGroup.Name[0] $False
                                }                                                
                            }
                        }
                    }
                } Catch {
                    $host.ui.WriteWarningLine(("GDGM{0}" -f $_.Exception.Message))
                }
            }
            #region Get Local Group Members
            $Script:Groups = @{}
            $Script:Counter=0
            # Bind to the group object with the WinNT provider.
            $ADSIGroup = [ADSI]"WinNT://$Computer/$Group,group"
            Write-Verbose ("Checking {0} membership for {1}" -f $Group,$Computer)
            $Groups[$Group] += ,'Local'
            Get-LocalGroupMember -LocalGroup $ADSIGroup
            #endregion Get Local Group Members
        }
        #endregion ScriptBlock
        Write-Verbose ("Checking to see if connected to a domain")
        Try {
            $Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
            $Root = $Domain.GetDirectoryEntry()
            $Base = ($Root.distinguishedName)

            # Use the NameTranslate object.
            $Script:Translate = New-Object -comObject "NameTranslate"
            $Script:objNT = $Translate.GetType()

            # Initialize NameTranslate by locating the Global Catalog.
            $objNT.InvokeMember("Init", "InvokeMethod", $Null, $Translate, (3, $Null))

            # Retrieve NetBIOS name of the current domain.
            $objNT.InvokeMember("Set", "InvokeMethod", $Null, $Translate, (1, "$Base"))
            [string]$Script:NetBIOSDomain =$objNT.InvokeMember("Get", "InvokeMethod", $Null, $Translate, 3)  
        } Catch {Write-Warning ("{0}" -f $_.Exception.Message)}         
        
        #region Runspace Creation
        Write-Verbose ("Creating runspace pool and session states")
        $sessionstate = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
        $runspacepool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionstate, $Host)
        $runspacepool.Open()  
        
        Write-Verbose ("Creating empty collection to hold runspace jobs")
        $Script:runspaces = New-Object System.Collections.ArrayList        
        #endregion Runspace Creation
    }

    Process {
        ForEach ($Computer in $Computername) {
            #Create the powershell instance and supply the scriptblock with the other parameters 
            $powershell = [powershell]::Create().AddScript($scriptBlock).AddArgument($computer).AddArgument($Group).AddArgument($Depth).AddArgument($NetBIOSDomain).AddArgument($ObjNT).AddArgument($Translate)
           
            #Add the runspace into the powershell instance
            $powershell.RunspacePool = $runspacepool
           
            #Create a temporary collection for each runspace
            $temp = "" | Select-Object PowerShell,Runspace,Computer
            $Temp.Computer = $Computer
            $temp.PowerShell = $powershell
           
            #Save the handle output when calling BeginInvoke() that will be used later to end the runspace
            $temp.Runspace = $powershell.BeginInvoke()
            Write-Verbose ("Adding {0} collection" -f $temp.Computer)
            $runspaces.Add($temp) | Out-Null
           
            Write-Verbose ("Checking status of runspace jobs")
            Get-RunspaceData @runspacehash   
        }
    }
    End {
        Write-Verbose ("Finish processing the remaining runspace jobs: {0}" -f (@(($runspaces | Where {$_.Runspace -ne $Null}).Count)))
        $runspacehash.Wait = $true
        Get-RunspaceData @runspacehash
    
        #region Cleanup Runspace
        Write-Verbose ("Closing the runspace pool")
        $runspacepool.close()  
        $runspacepool.Dispose() 
        #endregion Cleanup Runspace    
    }
}

Set-Alias -Name glgm -Value Get-LocalGroupMembership

我的脚本

$Servers = Get-ADComputer `
    -searchbase ‘OU=RDS Servers,OU=Production,OU=Servers,DC=xxx,DC=xxxx,DC=xxx’ `
    -properties OperatingSystem -Filter * | `
    Where-Object {$_.operatingsystem -Like "*Windows*"} | select name 

foreach ($Server in $Servers) {
    Get-LocalGroupMembership -Computername $Server.Name | `
        Export-Csv D:\Scripts\ServerLocalAdmin\Reports\LocalAdmins.csv `
        -Append -NoTypeInformation 
}

我得到的错误

Export-Csv : Cannot bind argument to parameter 'InputObject' because it is null.
At D:\Scripts\ServerLocalAdmin\ServerLocalAdmin.ps1:5 char:87
+ ... rver.Name | Export-Csv D:\Scripts\ServerLocalAdmin\Reports\LocalAdmin ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Export-Csv], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.Exp 
   ortCsvCommand
 
Export-Csv : Cannot bind argument to parameter 'InputObject' because it is null.
At D:\Scripts\ServerLocalAdmin\ServerLocalAdmin.ps1:5 char:87
+ ... rver.Name | Export-Csv D:\Scripts\ServerLocalAdmin\Reports\LocalAdmin ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Export-Csv], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.Exp 
   ortCsvCommand

【问题讨论】:

    标签: function powershell csv


    【解决方案1】:

    你只需要改变这个:

    foreach ($Server in $Servers)
    {
        Get-LocalGroupMembership -Computername $Server.Name |
        Export-Csv D:\Scripts\ServerLocalAdmin\Reports\LocalAdmins.csv -Append -NoTypeInformation
    }
    

    为此:

    foreach ($Server in $Servers)
    {
        $localMembership = Get-LocalGroupMembership -Computername $Server.Name
        
        if($localMembership)
        {
            $localMembership | Export-Csv D:\Scripts\ServerLocalAdmin\Reports\LocalAdmins.csv -Append -NoTypeInformation
        }
    }
    

    或者这个:

    foreach ($Server in $Servers)
    {
        $localMembership = Get-LocalGroupMembership -Computername $Server.Name
        
        if(-not $localMembership){continue}
    
        $localMembership | Export-Csv D:\Scripts\ServerLocalAdmin\Reports\LocalAdmins.csv -Append -NoTypeInformation
    }
    

    正如@Zucchini 提供的答案中所述,您遇到的错误是因为该函数为您的某些服务器返回了$null 值。要重现错误,您可以尝试:

    PS /~> $null | Export-Csv test.csv
    Export-Csv : Cannot bind argument to parameter 'InputObject' because it is null.
    At line:1 char:9
    + $null | Export-Csv test.csv
    +         ~~~~~~~~~~~~~~~~~~~
    ...
    

    值得一提的是,在每次循环迭代时将结果附加到 CSV 效率非常低,您的脚本具有的磁盘 I/O 越少,它运行的速度就越快:)

    因此,这是如何使脚本运行得更快的一个示例:

    
    $result = [system.collections.generic.list[pscustomobject]]::new()
    
    foreach ($Server in $Servers)
    {
        $localMembership = Get-LocalGroupMembership -Computername $Server.Name
        
        if($localMembership)
        {
            $result.Add($localMembership)
        }
    }
    
    $result | Export-Csv D:\Scripts\ServerLocalAdmin\Reports\LocalAdmins.csv -NoTypeInformation
    

    编辑:

    这是一个工作示例,该函数到处都返回空值。

    $result = [system.collections.generic.list[pscustomobject]]::new()
    
    foreach ($Server in $Servers)
    {
        $localMembership = Get-LocalGroupMembership -Computername $Server.Name
        
        foreach($i in $localMembership|?{$_})
        {
            $result.Add([pscustomobject]@{
                Name=$i.Name
                Depth=$i.Depth
                ParentGroup=$i.ParentGroup
                Type=$i.Type
                ComputerName=$i.ComputerName
                isGroup=$i.isGroup
            })
        }
    }
    
    $result | Export-Csv D:\Scripts\ServerLocalAdmin\Reports\LocalAdmins.csv -NoTypeInformation
    

    【讨论】:

    • 明天我会试一试,让您知道,感谢您的帮助。
    • 所以我浏览了你的每一个建议。首先,我遇到了与原始脚本相同的问题。对于第二个建议,我也得到了相同的行为。对于您提到的第三个建议会加快速度,我没有收到任何错误,但是输出不正确。它看起来像下面......
    • "Count","Length","LongLength","Rank","SyncRoot","IsReadOnly","IsFixedSize","IsSynchronized" "178","178","178" ,"1","System.Object[]","False","True","False" "185","185","185","1","System.Object[]","False ","真","假"
    • 我将尝试通过我知道正在连接的 csv 文件中的服务器列表运行测试。我正在测试的 OU 共有 13 台服务器。最终,我将通过另一个包含近 1500 台服务器的 OU 运行。我知道其中一些问题可能是 WinRM。这是一场试图确保通过多个子网和防火墙允许连接的战斗。我会在这方面与我们的网络团队进一步合作。
    • 在导入我知道是从 .csv 文件连接的服务器时,我遇到了同样的错误。因此,它似乎无法连接到服务器,这似乎不是问题。它一定是我假设的函数本身的东西。归根结底,我知道它有效,只是希望通过消除错误来加快速度。我已经对我的所有服务器运行过一次,大约需要 12 个小时。总的来说,这个功能非常棒,可以将所有用户添加到本地管理员组中。
    【解决方案2】:

    由于您正在迭代包含您的服务器的对象,而我们无权访问该数据,也许您应该尝试在 foreach 循环中添加一个输出,以查看对象中的哪些项目有问题。 听起来 Get-LocalGroupMembership 正在为您的某些服务器返回空值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-18
      • 2017-03-09
      • 1970-01-01
      • 1970-01-01
      • 2021-03-14
      • 1970-01-01
      • 2021-02-20
      • 1970-01-01
      相关资源
      最近更新 更多