Author Posts

July 18, 2018 at 2:50 pm

Greetings,

I'm having an issue getting a function to work properly. The goal is to compare the output of some JSON data to existing unified groups in Office 365, and if the group already exists, skip it, otherwise, create a new group. The tricky part is that as part of function that creates the group, it prepends "gr-" to the group name. Because the compare function is comparing the original JSON data without the prepended data to Office 365, the compare function has to have the logic to prepend "gr-" on the fly.

Here is the latest version of the function. There have been other variations, but none so far have worked. There are no errors, but the code does not identify lists that definitely do exist. I am using simple echo statements for the purpose of testing, the actual code will include the function to create a new group.

# Test variable that cycles through each .json file.
$jsonFiles = Get-ChildItem -path "c:\tmp\json" -filter *.json | get-content -raw
$allobjects = $jsonFiles | convertfrom-json

$alreadyCreatedGroup = Get-UnifiedGroup | select alias

# Determine if list already exists in Office 365
function checkForExistingGroup {
    [CmdletBinding()]
    Param(
        [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
        $InputObject
    )

    Process {
            if("gr-$($InputObject.alias)" -in $alreadyCreatedGroup){
                echo "$($inputobject.alias) exists"
                } 
                    else{
                        echo "$($InputObject.alias) does not exist"
                    }
            }
        }

$allobjects | checkForExistingGroup

The above code always produces the "does not exist" option for each alias from the JSON data.

The individual variables appear to be outputting correctly:

PS> $alreadyCreatedGroup

Alias                     
-----                     
gr-jsonoffice365grouptest1
gr-jsonoffice365grouptest2
gr-jsonoffice365grouptest3


PS> $allobjects.alias

jsonoffice365grouptest3
jsonoffice365grouptest4

If I run the following on its own:

"gr-$($allobjects.alias)"

I get the following output:

gr-jsonoffice365grouptest3 jsonoffice365grouptest4

So on its own it appends the output from the JSON files, but I had hoped by using $InputObject in the function, this would resolve that issue.

July 18, 2018 at 3:07 pm

As a note, we'd usually name this "Test-InExistingGroup" or something; confirming to PowerShell's naming conventions is an important part of using PowerShell :).

Be aware that echo is an alias to Write-Host; using Write-Verbose would actually be beneficial, since you could enable/disable that output by running (or not running) your function with -Verbose.

Also, the way you're using an out-of-scope reference to $alreadyCreatedGroup is a Very Bad Idea. Functions should NEVER rely on inputs that don't come to them as a parameter.

The ultimate problem you're having is this: The -in operator checks for the *entire object*. What you've got in $alreadyCreatedGroup is a bunch fo objects, and you're triggering on the Alias property. But what's coming in $inputObject are strings. Basically, you're not comparing apples to apples; you're comparing apples to grocery stores. You're attempting to compare simple strings to an entire object, so it never comes out true. You've selected only the alias property, true, but it's still a property of an object.

I'd suggest:

$alreadyCreatedGroup = Get-UnifiedGroup | select -Expand alias

# Determine if list already exists in Office 365
function Test-ForExistingGroup {
    [CmdletBinding()]
    Param(
        [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
        $InputObject,

        [Parameter(Position=0, Mandatory=$true)]
        $ExistingGroups
    )

    Process {
            if("gr-$($InputObject.alias)" -in $ExistingGroups) {
                Write-Verbose "$($inputobject.alias) exists"  
            } else{
                Write-Verbose "$($InputObject.alias) does not exist"
            }
    }
}

$allobjects | Test-ForExistingGroup -Verbose -Existing $alreadyCreatedGroup

– Conforming function name
– Switched to verbose output
– Extracted the contents of the Alias property into simple strings (using Select -Expand rather than just Select)
– Existing groups being fed to function as a parameter

July 18, 2018 at 3:23 pm

Outstanding, that works perfectly! Thank you for your guidance, it is greatly appreciated.

July 25, 2018 at 4:53 pm

Hello again,

I am attempting to use the same logic as above and this time it is not working for some reason. I am comparing a list of email addresses in a CSV file to all Azure AD external guest accounts in Office 365 and if it doesn't exist, create it. All external guest accounts will have the email address also as the displayname.

Here is what I currently have:

# Get email addresses (the CSV file has a header of externalGuest)
$Guests = import-csv c:\tmp\externalGuests.csv

# Get existing guest users:
$alreadyCreatedGuestUsers = Get-AzureADUser -Filter "userType eq 'Guest'" -all $true | select -Expand displayname

$timeFull = $((get-date).toString("yyyy-MM-dd--hh:mm:ss"))
$timeShort = $((get-date).toString("yyyy-MM-dd"))

filter timestamp {"$(Get-Date -Format yyy/MM/dd--hh:mm:ss): $_"}

function add-ExternalGuests {
    [CmdletBinding()]
    Param(
        [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
        $InputObject
    )

    Process {
             try
            {
                New-AzureADMSInvitation -ErrorAction continue `
                    -InvitedUserEmailAddress $InputObject.externalGuest `
                    -InvitedUserDisplayName $InputObject.externalGuest `
                    -SendInvitationMessage:$false `
                    -InviteRedirectUrl "https://myapps.microsoft.com" | `
                    select inviteduserdisplayname | `
                    timestamp | `
                    out-file -Append "c:\tmp\logs\$($timeShort)_add-ExternalGuests.log"
                    }
             catch
            {
                write-host "Guest NOT created:  $($InputObject.externalGuest)"
                $ErrorMessage = $_.Exception.Message
                $ErrorType = $_.Exception.gettype().fullname
                $ErrorItem = $_.Exception.ItemName
                write-output "$($timeFull):`r`nError trying to create: $($InputObject.externalGuest);`r`n[ERROR MESSAGE]:  
                 $($ErrorMessage)`r`n[ERROR TYPE]: $($ErrorType) `r`n `
                    $($InputObject.externalGuest) NOT created." `r`n | `
                out-file -append -filepath "c:\tmp\logs\$($timeShort)_add-ExternalGuests_error.log" 
                }
        }
}

function test-ExternalGuests {
    [CmdletBinding()]
    Param(
        [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
        $InputObject,

        [Parameter(Position=0, Mandatory=$true)]
        $createdGuests
    )

    Process {
                if($InputObject.externalGuest -notin $createdGuests){
                    # Write-Host "Creating external guest:  $($InputObject.externalGuest)"
                    $InputObject.externalGuest | add-ExternalGuests
                    }
                    else{
                            Write-Host "Guest already exists:  $($InputObject.externalGuest)"
                            write-output "Guest already exists:  $($InputObject.externalGuest)" | `
                            timestamp | `
                            out-file -Append "c:\tmp\logs\$($timeShort)_add-ExternalGuests.log"
                            }
            }
}

$Guests | test-ExternalGuests -Verbose -createdGuests $alreadyCreatedGuestUsers

The issue is with the test-ExternalGuests if/else logic. I know that 9 out of 10 of the email addresses from $guests exist, and the variables on their own output correctly. When I use -notin, all of them come back as not existing and it attempts to call the add-ExternalGuests function. If I prepend -not at the beginning of the If statement (if(-not $InputObject.externalGuest -in $createdGuests), they all trigger the Else block. If I try -contains, it also triggers the Else block for all. Using -notcontains triggers the add-ExternalGuests function for all 10.

I'm still experimenting, but I have a deadline approaching to get this to work, so I was hoping someone might have some recommendations on what might help.

Thank you in advance for your consideration.

July 25, 2018 at 5:04 pm

-in, -notin, -contains, and -notcontains are all basically the same thing. You've verified as much :).

Bugs in PowerShell come from one source: Either a property or a variable doesn't contain what you think it does. But you've no code in place to help figure out what they do, in fact, contain. And you kinda persist in using Write-Host and Write-Output :). You've called -Verbose, but you didn't include any calls to Write-Verbose, so it's useless. Let's fix it up a bit.

function test-ExternalGuests {
    [CmdletBinding(ConfirmImpact='Low', SupportsShouldProcess=$True)]
    Param(
        [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
        $InputObject,

        [Parameter(Position=0, Mandatory=$true)]
        $createdGuests
    )

    BEGIN {
      Write-Verbose "Already created: `n $createdGuests `n ---------"
    }

    Process {
      Write-Verbose "Testing '$($inputobject.externalguest)'"
                if($InputObject.externalGuest -notin $createdGuests){
                    Write-Verbose "Creating external guest:  '$($InputObject.externalGuest)'"
                    if ($pscmdlet.shouldprocess("$($InputObject.externalGuest)")) {
                      $InputObject.externalGuest | add-ExternalGuests
                    }
                    } else{
                            Write-Verbose "Guest already exists:  '$($InputObject.externalGuest)'"
                            out-file -Append "c:\tmp\logs\$($timeShort)_add-ExternalGuests.log"
                            }
            }
}

$Guests | test-ExternalGuests -Verbose -whatif -createdGuests $alreadyCreatedGuestUsers

Notice that I like to put 'single quotes' around my variable values. That way it's more apparent if one of them is empty – I'll see a " and know something's up.

I'd run this now, and I'd verify that the content of $createdGuests is in fact what I expected – that is, it's a list of strings that correspond to what $InputObject.externalguest would contain. I suspect that $createdGuests doesn't contain the same kinds of information as $InputObject.externalguest, which is why your comparison isn't doing what you think it should.

Notice that I've also added support for -WhatIf, so you can run this without ACTUALLY creating anything.

July 25, 2018 at 8:45 pm

Thanks again for the reply! You were right, it was a variable that was the issue. Turns out it was a malformed CSV file.