Search code examples
stringpowershellpipelineenumeration

Two objects of different sizes and $null


My code below works in every instance except for if one object is $null and the other object has one item. When that situation occurs the output becomes 1 letter like it is indexing and I am not sure why. How do I combine the two objects to make a final report?

$ADGroups = Get-ADPrincipalGroupMembership -Identity $UserSam | Select-Object distinguishedName, name | Where-Object { ($_.distinguishedName -ne 'CN=Domain Users,CN=Users,DC=com') }

#record AD groups
$ADResult = @()
if ($null -eq $ADGroups) {
    Write-Warning "No AD Groups"
    $ADResult = [PSCustomObject]@{ 
        ADGroups                  = @()
        ADGroupsdistinguishedName = @()
    }
}
Else {
    $ADResult = $ADGroups | ForEach-Object {
        [PSCustomObject]@{
            ADGroups                  = $_.name
            ADGroupsdistinguishedName = $_.distinguishedName
        }
    }
}

#============= Now Google, get user groups and record
$GoogleGroups = gam print groups member $email members managers owners | ConvertFrom-Csv

# Record Google Groups
$GResult = @()
If ($null -eq $GoogleGroups) {
    Write-Warning "No Google Groups"
    $GResult = [PSCustomObject]@{
        GoogleGroups = @()
        Role         = @()
    }
}
Else {
    $group = $null
    $GResult = ForEach ($group in $GoogleGroups) {
        #this records what role the user had in the group(s)
        $GoogleMember = gam print group-members group $group.email members | ConvertFrom-Csv | Select-Object -ExpandProperty email
        $Role = $null
        If ( $GoogleMember -contains $EMAIL) {
            $Role = 'Member'
        }
        Else {
            $GoogleManager = gam print group-members group $group.email managers | ConvertFrom-Csv | Select-Object -ExpandProperty email
            If ($GoogleManager -contains $EMAIL) {
                $Role = 'Manager'
            }
            Else {
                $Role = 'Owner'
            }
        }
        [PSCustomObject]@{
            GoogleGroups = $group.email
            Role         = $role
        }
        $group = $null
    }
}

# ---------now report that will be dropped off at end
[int]$max = $ADResult.count
if ([int]$GResult.count -gt $max) { [int]$max = $GResult.count }

If ($max -eq 1 -or $max -eq 0) {
    $Result = [PSCustomObject]@{ 
        PrimaryEmail                 = $email
        Title                        = $UserInfo.title
        Department                   = $UserInfo.Department
        Manager                      = $Manager
        ADGroupName                  = $ADResult.ADGroups
        ADGroupNameDistinguishedName = $ADResult.ADGroupsdistinguishedName
        GoogleGroup                  = $GResult.GoogleGroups
        Role                         = $GResult.role
        DateOfSeparation             = (Get-Date).ToString("yyyy_MM_dd")
        UserDistinguishedName        = $UserInfo.distinguishedName
        UserOU                       = $UserInfo.Ou
        PrimaryGroup                 = $UserInfo.primaryGroup.Split('=').Split(',')
    }
}
Else {
    $Result = for ( $i = 0; $i -lt $max; $i++) {
        [PSCustomObject]@{ 
            PrimaryEmail                 = $email
            Title                        = $UserInfo.title
            Department                   = $UserInfo.Department
            ADGroupName                  = $ADResult.ADGroups[$i]
            ADGroupNameDistinguishedName = $ADResult.ADGroupsdistinguishedName[$i]
            GoogleGroup                  = $GResult.GoogleGroups[$i]
            Role                         = $GResult.role[$i]
            DateOfSeparation             = (Get-Date).ToString("yyyy_MM_dd")
            UserDistinguishedName        = $UserInfo.distinguishedName
            UserOU                       = $UserInfo.Ou
            PrimaryGroup                 = $UserInfo.primaryGroup.Split('=').Split(',')[$i]
        }
    }
}


$Result | Export-Csv 'C:\temp\Groups.csv' -NoTypeInformation

Solution

  • Going by the abstract description of your problem:

    You're seeing an unfortunate asymmetry in PowerShell:

    • In the pipeline, a [string] instance is considered a single object.

      PS> ('foo' | Measure-Object).Count
      1
      
    • With respect to indexing, it is considered an array of characters.

      PS> 'foo'[0]
      f
      

    A general feature of capturing a PowerShell pipeline's output is that if a command situationally outputs just a single object, that object is captured as-is, whereas two or more output objects result in a regular PowerShell array, of type [object[]].

    • Typically, this isn't a problem, because PowerShell's unified handling of scalars and collections allows you to index even into a scalar (single object), i.e. to implicitly treat a single object as if it were a single-element array:

      PS> (Write-Output 42, 43)[0]
      42
      
      PS> (Write-Output 42)[0]
      42  # still OK, even though only *one* object was output; same as: (42)[0]
      
    • However, with a single [string] instance as the output it becomes a problem, for the reasons stated above:

      PS> (Write-Output 'foo', 'bar')[0]
      foo # OK
      
      PS> (Write-Output 'foo')[0]
      f   # !! Indexing into a *single string* treats it as *character array*
      

    The same applies to values returned via member-access enumeration, perhaps surprisingly :

    PS> (Get-Item $HOME, /).FullName[0]
    C:\Users\Jdoe
    
    PS> (Get-Item $HOME).FullName[0]
    C # !! Indexing into a *single string* treats it as *character array*
    

    Workarounds:

    • Enclose the command of interest in @(...), the array-subexpression operator so as to ensure that its output is always considered an array.

      PS> @(Write-Output 'foo')[0]
      foo # OK
      
    • Alternatively, when capturing a command's output in a variable, type-constrain that variable to [array] (same as [object[]]) or a strongly typed array, [string[]]:

      PS> [array] $output = Write-Output 'foo'; $output[0]
      foo # OK