Passing multiple parameters to a function and returning multiple values

Hi All,

I am trying to rewrite an existing program to use a function since I need to run the same steps more than once. I am running into issues passing values in and out of the function. My input file has three columns IDNum,UPN,Role. I am updating the script variables and creating a list of UPNs for each Role. I keep getting

 Compare-Object : Cannot bind argument to parameter 'DifferenceObject' because it is null.
At C:\Util\Update-Roles.ps1:79 char:86
+ ... t -ReferenceObject $currentGroupUsers -DifferenceObject $updatedUsers
+                                                             ~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Compare-Object], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.CompareObjectCommand 

I have tried casting the parameters in the prototype using string, array

function name([type]varname1,[type]varname2){
}

as well as

function name() {
Param(
[type]$varname1,
[type]$varname2
)

}

Nothing has worked to get values into the function.

$groupAdmin = "Admin-Group"
$groupAdvisor =  "Advisor-Group"
$updatedUserFile = "D:\<path>\update.csv"

$csvAdmin = ""
$csvAdvisor = ""

$adminRemoved = 0
$adminAdded = 0
$advisorRemoved = 0
$advisorAdded = 0

 function updateGroup($csvUsers, $groupName) {

    $usersAdded = 0
    $usersRemoved = 0

    $updatedUsers = ForEach ($user in $csvUsers) {
        # Get AD user object based on UPN and add object to group
        Get-ADUser -Filter {userPrincipalName -eq $user } -Properties DistinguishedName
    }

    # Build list of current group members
    $currentGroupUsers = (Get-ADGroup $groupName -Properties member).member

    # Create file with differences between current users and users from user file  
    $modGroup = Compare-Object -ReferenceObject $currentGroupUsers -DifferenceObject $updatedUsers

    # Add/Remove based on difference between current and updated users list
    $modGroup | foreach {

        # Remove uses from group
        if ($_.sideindicator -eq '<='){
            $DN = $_.InputObject
            $usersRemoved++
            Remove-ADGroupMember -Identity $groupName -Members "$DN" -Confirm:$false
        }

        # Add users to group
        if ($_.sideindicator -eq '=>'){
            $DN = $_.InputObject
            $usersAdded++
            Add-ADGroupMember -Identity $groupName -Members "$DN"
        }
    }
    return $usersRemoved, $usersAdded
} 

 # Build sorted membership lists from input file
Import-Csv $updatedUserFile | ForEach-Object {
    $user = $_."UPN"
    $user = $user.trim()
    $user = $user.Trim('"')

    switch ($_.Role) {
        admin {
            $script:csvAdmin += $user
        }
        advisor {
            $script:csvAdvisor += $user
        }
    }
}

$returnedAdminCnt = updateGroup $csvAdmin $groupAdmin;
$adminRemoved = $returnedAdminCnt[0];
$adminAdded = $returnedAdminCnt[1];

$returnedAdvisorCnt = updateGroup $csvAdvisor $groupAdvisor;
$advisorRemoved = $returnedAdvisorCnt[0];
$advisorAdded = $returnedAdvisorCnt[1];

Thanks in advance

Chris,
Welcome back to the forum. :wave:t4: … long time no see. :wink:

It might be a better way to start new from scratch instead of trying to improve or migrate old and potentially low quality code. :wink:

The error message is pretty obvious, isn’t it? :smirk: To check your variables you should use Write-Debug and output the variables to the console during runtime. How do you populate the values you pass as parameter $csvUsers?

What did you use as [type]? And if you want to use an array it should be [type[]] instead.

I urgently recommend to read

… and write your code much more verbose.

The array you create for the variable $updatedUsers is an array of objects with properties while the array you create for the variable $currentGroupUsers is just strings. They should be at least of the same type and when you use objects you should specify the property you want to compare on.

Hi Olaf,

Thanks for the feedback.

I tried type casting as string and as an array. I even used .getType to identify the right type plus moved my Get-ADUser out of the function and passed in an array of objects. Ultimately I didn’t get it to work which is why I left the type off in my current code. Inside the function $csvUsers and $groupName are null. The code works if it’s inline. If I simplify it I am not seeing output

 function updateGroup($csvUsers, $groupName) {

Write-Debug $csvUsers
Write-Debug $groupName

    $usersAdded = 0
    $usersRemoved = 0
 
     return $usersRemoved, $usersAdded
} 

$returnedAdminCnt = updateGroup $csvAdmin $groupAdmin;

Do you have an example of passing in a list of UPNs and a string to a function?

Using the same approach again and again and expecting different results is pointless.

Input data:

$UPNList = @'
UPN
John@beatles.co.uk
George@beatles.co.uk
Paul@beatles.co.uk
Ringo@beatles.co.uk
'@ |
    ConvertFrom-Csv

Function:

function Show-SingleValue {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)]
        [Alias('UserPrincipalName', 'UPN', 'Name')]
        [string[]]
        $User,
        [Parameter(Mandatory = $true)]
        [string]
        $ADGroup
    )

    process {
        foreach ($SingleUser in $User) {
            [PSCustomObject]@{
                User  = $SingleUser
                Group = $ADGroup
            }
        }
    }
}

Sample use case 1 providing values directly to the parameters:

Show-SingleValue -User $UPNList.UPN -ADGroup SupiDupiAdGroup

Sample use case 2 using pipeline:

$UPNList | Show-SingleValue -ADGroup BestAdGroupEver