Invoke-SNSUppdateOffice365User.ps1

function Invoke-SNSUppdateOffice365User
{
    <#
    .SYNOPSIS
        Main function for syncronisation of office 365 users with scoutnet.

    .DESCRIPTION
        Fetches the distribution groups members and updates corressponding distribution groups based on the info from scoutnet.
        As all members of an distribution group must be present in exchange as user or contact, contacts will be created for external addresses.

    .INPUTS
        None. You cannot pipe objects to Invoke-SNSUppdateOffice365User.

    .OUTPUTS
        None.

    .LINK
        https://github.com/scouternasetjanster/Office365-Scoutnet-synk

    .PARAMETER Configuration
        Configuration to use. If not specified the cached configuration will be used.
    #>

    [CmdletBinding(HelpURI = 'https://github.com/scouternasetjanster/Office365-Scoutnet-synk',
                PositionalBinding = $False)]
    param (
        [Parameter(Mandatory=$False, HelpMessage="Configuration to use. If not specified the cached configuration will be used.")]
        $Configuration
    )

    if ($Configuration)
    {
        $Script:SNSConf = $Configuration
    }

    if (!$Script:SNSConf)
    {
        throw "No configuration specified. Please provide a configuration!"
    }

#region Create licensing data
    $userLicenseData = New-Object System.Collections.ArrayList
    try
    {
        foreach($licensepack in $Script:SNSConf.LicenseAssignment.keys)
        {
            $packSku = Get-MgSubscribedSku -All | Where-Object SkuPartNumber -eq $licensepack
            $skudata = @{SkuId = $packSku.SkuId}
            $disabledplans = $packSku.ServicePlans | Where-Object ServicePlanName -in $Script:SNSConf.LicenseAssignment[$licensepack] | Select-Object -ExpandProperty ServicePlanId
            if ($disabledplans)
            {
                $skudata = @{SkuId = $packSku.SkuId
                            DisabledPlans = $disabledplans
                            }
            }
            [Void]$userLicenseData.add($skudata)
        }
    }
    catch
    {
        Write-SNSLog -Level "Error" "Could not create user license data. Error $_"
        return
    }
#endregion

    Write-SNSLog "Start of user account update"

    # If there is only one account that mailbox is returned. Not a list.
    $allMailboxes = Get-EXOMailbox -RecipientTypeDetails "UserMailbox" -Properties CustomAttribute1 -Verbose:$false
    if ($allMailboxes -is [System.Array])
    {
        [System.Collections.ArrayList]$allOffice365Users = $allMailboxes
    }
    else
    {
        # Only one mailbox. Create empty arraylist and add the mailbox.
        [System.Collections.ArrayList]$allOffice365Users = @()
        [void]$allOffice365Users.Add($allMailboxes)
    }

    [System.Collections.ArrayList]$SecurityGroupScoutnet = Get-SNSUsersInSecurityGroupScoutnet -allOffice365Users $allOffice365Users
    [System.Collections.ArrayList]$UsersInSecurityGroupScoutnetDisabledUsers = Get-SNSUsersInSecurityGroupScoutnetDisabledUsers -allOffice365Users $allOffice365Users
    [System.Collections.ArrayList]$MemberListScoutnet = Get-SNSSoutnetLeaders -CredentialCustomlists $Script:SNSConf.CredentialCustomlists -CredentialMemberlist $Script:SNSConf.CredentialMemberlist -MailListId $Script:SNSConf.UserSyncMailListId
    $GroupMemberlist = Get-SNSApiGroupMemberlist -Credential $Script:SNSConf.CredentialMemberlist

    if (!$GroupMemberlist)
    {
        throw "No data returned from Scoutnet. Update aborted."
    }

    Write-SNSLog "Total number of office365 users: $($allOffice365Users.Count)"
    Write-SNSLog "Total number of Scoutnet members: $($MemberListScoutnet.Count)"

    Write-SNSLog "Start check"

    $NewMembers = [System.Collections.ArrayList]::new()
    $MembersToUpdate = [System.Collections.ArrayList]::new()
    $MembersToActivate = [System.Collections.ArrayList]::new()
    foreach($Member in $MemberListScoutnet)
    {
        # Check if the member do have an account.
        $account = $allOffice365Users | Where-Object -FilterScript {$_.CustomAttribute1 -like $Member}
        $isInSecurityGroupScoutnet = $SecurityGroupScoutnet | Where-Object -FilterScript {$_.CustomAttribute1 -like $Member}
        $isInUsersInSecurityGroupScoutnetDisabledUsers = $UsersInSecurityGroupScoutnetDisabledUsers | Where-Object -FilterScript {$_.CustomAttribute1 -like $Member}

        if ($account)
        {
            if ($isInSecurityGroupScoutnet)
            {
                # The member is in SecurityGroupScoutnet. Update office 365 account if needed.
                [void]$MembersToUpdate.Add($account)
                $SecurityGroupScoutnet.RemoveAt($SecurityGroupScoutnet.IndexOf($isInSecurityGroupScoutnet))
            }
            elseif ($isInUsersInSecurityGroupScoutnetDisabledUsers)
            {
                # Returning member. Enable and update the office 365 account.
                [void]$MembersToActivate.Add($isInUsersInSecurityGroupScoutnetDisabledUsers)
                $UsersInSecurityGroupScoutnetDisabledUsers.RemoveAt($UsersInSecurityGroupScoutnetDisabledUsers.IndexOf($isInUsersInSecurityGroupScoutnetDisabledUsers))
            }
            else
            {
                Write-SNSLog "Account not to update: '$($account.Name)'"
            }

            # Remove from list of all accounts.
            $allOffice365Users.RemoveAt($allOffice365Users.IndexOf($account))
        }
        else
        {
            # New member. Add account.
            $MemberData = $GroupMemberlist.data[$Member]
            if ($null -ne $MemberData)
            {
                [void]$NewMembers.Add($MemberData)
            }
            else
            {
                Write-SNSLog -Level "Warn" "Scoutnet maillist contained an entry that is not an member. Member number '$Member'"
            }
        }
    }

    Write-SNSLog "Start check done"
    Write-SNSLog "Number of accounts to create: $($NewMembers.Count)"
    if ($NewMembers.Count -gt 0)
    {
        try
        {
            Invoke-SNSCreateUserAndUpdateUserData -memberData $NewMembers -userLicenseData $userLicenseData
        }
        catch
        {
            Write-SNSLog -Level "Error" "Error during user account creation. Error $_"
        }
    }

    Write-SNSLog "Number of accounts to activate: $($MembersToActivate.Count)"
    $MembersToActivate | ForEach-Object {
        Invoke-SNSEnableAccount -AccountData $_
        [void]$MembersToUpdate.Add($_)
    }

    Write-SNSLog "Number of disabled accounts: $($UsersInSecurityGroupScoutnetDisabledUsers.Count)"
    if ($UsersInSecurityGroupScoutnetDisabledUsers.Count -gt 0)
    {
        $UsersInSecurityGroupScoutnetDisabledUsers | ForEach-Object {
            Write-SNSLog "Disabled account '$($_.Name)'"
            $allOffice365Users.RemoveAt($allOffice365Users.IndexOf($_))
        }
    }

    Write-SNSLog "Number of accounts to disable: $($SecurityGroupScoutnet.Count)"
    $SecurityGroupScoutnet | ForEach-Object {
        Invoke-SNSDisableAccount -AccountData $_
        $allOffice365Users.RemoveAt($allOffice365Users.IndexOf($_))
    }

    Write-SNSLog "Number of accounts to check for update: $($MembersToUpdate.Count)"
    $MembersToUpdate | ForEach-Object {
        Invoke-SNSUpdateAccount -AccountData $_ -Credential $Script:SNSConf.CredentialMemberlist
    }

    Write-SNSLog "Number of accounts not connected to Scoutnet: $($allOffice365Users.Count)"
    $allOffice365Users | ForEach-Object {Write-SNSLog "Account not connected to Scoutnet '$($_.Name)'"}
}

function Get-RandomPassword
{
    <#
    .SYNOPSIS
        Generates a random password of given length.

    .INPUTS
        None. You cannot pipe objects to Get-RandomPassword.

    .OUTPUTS
        None.

    .PARAMETER length
        Lenght of password to create.
    #>

    [OutputType([string])]
    param (
        [Parameter(Mandatory=$true, HelpMessage="Lenght of password to create.")]
        [int] $length
    )
    $charSet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'.ToCharArray()
    $rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider
    $bytes = New-Object byte[]($length)

    $rng.GetBytes($bytes)

    $result = New-Object char[]($length)

    for ($i = 0 ; $i -lt $length ; $i++)
    {
        $result[$i] = $charSet[$bytes[$i]%$charSet.Length]
    }

    return (-join $result)
}

function Invoke-SNSCreateUserAndUpdateUserData
{
    <#
    .SYNOPSIS
        Creates and updates the new account.

    .INPUTS
        None. You cannot pipe objects to Invoke-SNSCreateUserAndUpdateUserData.

    .OUTPUTS
        None.
    #>

    param (
        [Parameter(Mandatory=$true, HelpMessage="List of members to create.")]
        [ValidateNotNull()]
        $memberData,
        [Parameter(Mandatory=$true, HelpMessage="Licensing data")]
        [ValidateNotNull()]
        $userLicenseData
    )

    $SecurityGroupScoutnet = Get-SNSSecurityGroupScoutnet

    $newAccounts = [ordered]@{}
    $LastAccountUserPrincipalName=$null
    foreach($MemberData in $memberData)
    {
#region Generate the new UserPrincipalName
        $properties = "businessPhones, displayName, givenName, id, jobTitle, mail, mobilePhone, UserPrincipalName, preferredLanguage, surname, userPrincipalName, postalCode, identities, UserType, StreetAddress, City, Department, UserType"
        $DisplayName = "$($MemberData.first_name.value) $($MemberData.last_name.value)"

        Write-SNSLog "Trying to create account for '$($DisplayName)' with id '$($MemberData.member_no.value)'."

        $UserName = "$($MemberData.first_name.value).$($MemberData.last_name.value)".ToLower()
        # Convert UTF encoded names and create corresponding ASCII version.
        $UserName = [Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding("Cyrillic").GetBytes($UserName))

        $UserPrincipalName = "$($UserName)@$($Script:SNSConf.DomainName)"
        # Remove any blanks, space, tab...
        $UserPrincipalName = $UserPrincipalName -replace '\s+'
        $MailNickname = $UserName -replace '\s+'

        $office365User  = Get-MgUser -UserId $UserPrincipalName -Property $properties -ErrorAction SilentlyContinue

        if ($office365User)
        {
            # Mailaddress alredy exists. Try with an extra number.
            For ($cnt=1; $cnt -le 5; $cnt++)
            {
                $MailNickname = "$($UserName).$($cnt)"
                $MailNickname = $MailNickname -replace '\s+'
                $UserPrincipalName = "$($MailNickname)@$($Script:SNSConf.DomainName)"
                $office365User  = Get-MgUser -UserId $UserPrincipalName -Property $properties -ErrorAction SilentlyContinue
                if (!$office365User)
                {
                    # Mailaddress not used. Use it!
                    break
                }
            }
        }
#endregion

        if (!$office365User)
        {
#region Fetch data for the new user
            try
            {
                $UserPassword = Get-RandomPassword 15
                $createUserparams = @{
                    AccountEnabled = $true
                    DisplayName = $DisplayName
                    UserPrincipalName = $UserPrincipalName
                    MailNickname = $MailNickname
                    GivenName = $MemberData.first_name.value
                    Surname = $MemberData.last_name.value
                    PreferredLanguage = $Script:SNSConf.PreferredLanguage
                    UsageLocation = $Script:SNSConf.UsageLocation
                    PasswordProfile = @{
                        ForceChangePasswordNextSignIn = $true
                        Password = $UserPassword
                    }
                }

                if (-not [string]::IsNullOrEmpty($MemberData.postcode.value))
                {
                    # Only add PostalCode if it exists in scoutnet.
                    $createUserparams["PostalCode"] = $MemberData.postcode.value
                }
                else
                {
                    Write-SNSLog -Level "Warn" "No 'postcode' for member '$DisplayName' with id '$($MemberData.member_no.value)'"
                }

                if (-not [string]::IsNullOrEmpty($MemberData.town.value))
                {
                    # Only add City if it exists in scoutnet.
                    $createUserparams["City"] = $MemberData.town.value
                }
                else
                {
                    Write-SNSLog -Level "Warn" "No 'town' for member '$DisplayName' with id '$($MemberData.member_no.value)'"
                }

                if (-not [string]::IsNullOrEmpty($MemberData.country.value))
                {
                    # Only add Country if it exists in scoutnet.
                    $createUserparams["Country"] = $MemberData.country.value
                }
                else
                {
                    Write-SNSLog -Level "Warn" "No 'country' for member '$DisplayName' with id '$($MemberData.member_no.value)'"
                }

                if (-not [string]::IsNullOrEmpty($MemberData.contact_mobile_phone.value))
                {
                    # Only add MobilePhone if it exists in scoutnet.
                    $createUserparams["MobilePhone"] = $MemberData.contact_mobile_phone.value
                }
                else
                {
                    Write-SNSLog -Level "Warn" "No 'MobilePhone' for member '$DisplayName' with id '$($MemberData.member_no.value)'"
                }

                $StreetAddress = $MemberData.address_1.value
                if ($MemberData.address_2.value)
                {
                    $StreetAddress += " " + $MemberData.address_2.value
                }
                if ($MemberData.address_3.value)
                {
                    $StreetAddress += " " + $MemberData.address_3.value
                }

                if (-not [string]::IsNullOrEmpty($StreetAddress))
                {
                    # Only add StreetAddress if it exists in scoutnet.
                    $createUserparams["StreetAddress"] = $StreetAddress
                }
                else
                {
                    Write-SNSLog -Level "Warn" "No 'StreetAddress' for member '$DisplayName' with id '$($MemberData.member_no.value)'"
                }

                $OtherMails = New-Object System.Collections.ArrayList
                if (-not [string]::IsNullOrEmpty($MemberData.email.value))
                {
                    if ($UserPrincipalName -notlike $MemberData.email.value)
                    {
                        # email usable.
                        [void]$OtherMails.Add($MemberData.email.value)
                    }
                }

                if (-not [string]::IsNullOrEmpty($MemberData.contact_alt_email.value))
                {
                    if ($UserPrincipalName -notlike $MemberData.contact_alt_email.value)
                    {
                        # contact_alt_email usable.
                        [void]$OtherMails.Add($MemberData.contact_alt_email.value)
                    }
                }

                if ($OtherMails.Count -gt 0)
                {
                    $createUserparams["OtherMails"] = $OtherMails.ToArray()
                }
            }
            catch
            {
                Write-SNSLog -Level "Error" "Could not fetch data for user '$($UserPrincipalName)' for member '$DisplayName' with id '$($MemberData.member_no.value)'. Error $_"
                continue
            }
#endregion

#region Create the new user.
            try
            {
                $newAccount = New-MgUser -BodyParameter $createUserparams -ErrorAction Stop
                Set-MgUserLicense -UserId $UserPrincipalName -Addlicenses $userLicenseData.ToArray() -RemoveLicenses @()
                Write-SNSLog "User '$($newAccount.UserPrincipalName)' added for member id '$($MemberData.member_no.value)'."
            }
            catch
            {
                Write-SNSLog -Level "Error" "Could not create user '$($UserPrincipalName)' for member '$DisplayName' with id '$($MemberData.member_no.value)'. Error $_"
                continue
            }
#endregion

#region Add the user to the group of active users.
            [void]$newAccounts.Add($MemberData.member_no.value, @($MemberData, $newAccount, $UserPassword))
            $LastAccountUserPrincipalName = $newAccount.UserPrincipalName
            try
            {
                New-MgGroupMember -GroupId $SecurityGroupScoutnet.Id -DirectoryObjectId $newAccount.Id -ErrorAction "Stop"
            }
            Catch
            {
                Write-SNSLog -Level "Warn" "Could not add contact '$($newAccount.DisplayName)' to group $($Script:SNSConf.SyncGroupName). Error $_"
                continue
            }
#endregion
        }
        else
        {
            Write-SNSLog -Level "Error" "Mailaddress $($UserPrincipalName) is alredy in use. Can not add a user for member '$DisplayName' with id '$($MemberData.member_no.value)'"
        }
    }

#region Wait for mailbox creation.
    $maxDateTimeout = (Get-Date).AddSeconds($Script:SNSConf.WaitMailboxCreationMaxTime)
    if (!$LastAccountUserPrincipalName)
    {
        Write-SNSLog "No user created."
        return
    }
    else
    {
        $doLoop = $true
        Write-SNSLog "Wait for the mailbox for the new users to be created. It can take som time..."
        Start-Sleep -s $Script:SNSConf.WaitMailboxCreationPollTime
    }

    try
    {
        while($doLoop)
        {
            Start-Sleep -s $Script:SNSConf.WaitMailboxCreationPollTime
            try
            {
                Get-EXOMailbox -Identity $LastAccountUserPrincipalName -RecipientTypeDetails "UserMailbox" -ErrorAction "Stop" > $null
                # Mailboxes is created.
                $doLoop=$false
                break
            }
            catch
            {
                Write-SNSLog "Still waiting..."
            }

            if ($maxDateTimeout -lt (Get-Date))
            {
                # timeout limit reached so exception
                $msg = "The creation of user mailboxes did not"
                $msg += "complete within the timeout limit of "
                $msg += "$($Script:SNSConf.WaitMailboxCreationMaxTime) seconds, so polling "
                $msg += "for mailbox creation was halted."
                throw ($msg)
            }
        }
#endregion

        foreach($newAccountId in $newAccounts.Keys)
        {
#region Update the mailbox configuration for the new account.
            $member       = $newAccounts[$newAccountId][0]
            $newAccount   = $newAccounts[$newAccountId][1]
            $UserPassword = $newAccounts[$newAccountId][2]

            Write-SNSLog "Updating account '$($newAccount.DisplayName)'."

            $SignatureHtml = $Script:SNSConf.SignatureHtml -Replace "<DisplayName>", $newAccount.DisplayName
            $SignatureText = $Script:SNSConf.SignatureText -Replace "<DisplayName>", $newAccount.DisplayName

            try
            {
                Set-Mailbox -Identity $newAccount.UserPrincipalName -CustomAttribute1 $newAccountId -ErrorAction "Stop"
                Set-MailboxMessageConfiguration $newAccount.UserPrincipalName -IsReplyAllTheDefaultResponse $false `
                    -SignatureHtml $SignatureHtml -SignatureText $SignatureText -AutoAddSignature $true -AutoAddSignatureOnMobile $true `
                    -AutoAddSignatureOnReply $true -SignatureTextOnMobile $SignatureText -ErrorAction "Stop"
            }
            catch
            {
                Write-SNSLog -Level "Error" "Could not update mailbox for user '$($newAccount.UserPrincipalName)'. Error $_"
            }
#endregion

#region Send e-mail to the user with the new password and account info
            if ([string]::IsNullOrWhiteSpace($member.email.value))
            {
                Write-SNSLog "No valid email address in scoutnet for user '$($newAccount.UserPrincipalName)'. Notify the user about the new account."
            }
            else
            {
                try
                {
                    # Send e-mail to the user with the new password and account info. The password must be replaced at first login.
                    $NewUserEmailText = $Script:SNSConf.NewUserEmailText -Replace "<DisplayName>", $newAccount.DisplayName -Replace "<Password>", $UserPassword -Replace "<UserPrincipalName>", $newAccount.UserPrincipalName
                    $params = @{
                        Message = @{
                            Subject = $Script:SNSConf.NewUserEmailSubject
                            importance = "High"
                            isDeliveryReceiptRequested = "True"
                            isReadReceiptRequested = "True"
                            Body = @{
                                ContentType = $Script:SNSConf.NewUserEmailContentType
                                Content = $NewUserEmailText
                            }
                            ToRecipients = @(
                                @{
                                    EmailAddress = @{
                                        Address = $member.email.value
                                    }
                                }
                            )
                            Flag = @{
                                flagStatus="flagged"
                            }
                        }
                        SaveToSentItems = "true"
                    }
                    Send-MgUserMail -UserId $Script:SNSConf.EmailFromAddress -BodyParameter $params
                }
                Catch
                {
                    Write-SNSLog -Level "Warn" "Could not send email to $($member.email.value). Error $_"
                }
            }
#endregion

#region Extra info mail requested. Send it to the new account.
            if (![string]::IsNullOrWhiteSpace($Script:SNSConf.NewUserInfoEmailText) -and ![string]::IsNullOrWhiteSpace($Script:SNSConf.NewUserInfoEmailSubject))
            {
                try
                {
                    # Extra info mail requested. Send it to the new account.
                    $NewUserEmailText = $Script:SNSConf.NewUserInfoEmailText -Replace "<DisplayName>", $newAccount.DisplayName -Replace "<UserPrincipalName>", $newAccount.UserPrincipalName
                    $params = @{
                        Message = @{
                            Subject = $Script:SNSConf.NewUserInfoEmailSubject
                            importance = "High"
                            isDeliveryReceiptRequested = "False"
                            isReadReceiptRequested = "True"
                            Body = @{
                                ContentType = $Script:SNSConf.NewUserInfoEmailContentType
                                Content = $NewUserEmailText
                            }
                            ToRecipients = @(
                                @{
                                    EmailAddress = @{
                                        Address = $newAccount.UserPrincipalName
                                    }
                                }
                            )
                            Flag = @{
                                flagStatus="flagged"
                            }
                        }
                        SaveToSentItems = "true"
                    }
                    Send-MgUserMail -UserId $Script:SNSConf.EmailFromAddress -BodyParameter $params -ErrorAction "Stop"
                }
                Catch
                {
                    Write-SNSLog -Level "Warn" "Could not send email to $($newAccount.UserPrincipalName). Error $_"
                }
            }
#endregion

#region Add the user to the Distribution Group for all users with office 365 account.
            if ($Script:SNSConf.AllUsersGroupName)
            {
                try
                {
                    # Add the user to the Distribution Group for all users with office 365 account.
                    Add-DistributionGroupMember -Identity $Script:SNSConf.AllUsersGroupName -Member $newAccount.UserPrincipalName -ErrorAction "Stop"
                    Write-SNSLog "The account '$($newAccount.DisplayName)' is added to distribution group '$($Script:SNSConf.AllUsersGroupName)'"
                }
                Catch
                {
                    if ($_.CategoryInfo.Reason -ne "MemberAlreadyExistsException")
                    {
                        Write-SNSLog -Level "Warn" "Could not add contact $($newAccount.UserPrincipalName) to group $($Script:SNSConf.AllUsersGroupName). Error $_"
                    }
                }
            }
            Write-SNSLog "The account for '$($newAccount.DisplayName)' is updated and ready for use."
#endregion
        }
    }
    catch
    {
        Write-SNSLog -Level "Error" "Error during user account creation. Error $_"
    }
}

function Invoke-SNSUpdateAccount
{
    <#
    .SYNOPSIS
        Updates the account with information from Scoutnet, if the data is changed.

    .INPUTS
        None. You cannot pipe objects to Get-SNSUpdateAccount.

    .OUTPUTS
        None.
    #>

    param (
        [Parameter(Mandatory=$false, HelpMessage="User to update.")]
        [ValidateNotNull()]
        $AccountData,

        [Parameter(Mandatory=$False, HelpMessage="Credentials for api/group/memberlist.")]
        [ValidateNotNull()]
        [pscredential]$CredentialMemberlist

    )

    try
    {
        $properties = "OtherMails, businessPhones, displayName, givenName, id, jobTitle, mail, mobilePhone, UserPrincipalName, preferredLanguage, surname, userPrincipalName, postalCode, identities, UserType, StreetAddress, City, Department, UserType, Country"
        $GroupMemberlist = Get-SNSApiGroupMemberlist -Credential $CredentialMemberlist
        $MemberData = $GroupMemberlist.data[$AccountData.CustomAttribute1]
        $O365MemberData =  Get-MgUser -UserId $AccountData.ExternalDirectoryObjectId -Property $properties -ErrorAction "Stop"

        if ($MemberData)
        {
            # Create empty structure.
            $updateUserparams = @{}

#region Handle updated address data.
            $StreetAddress = $MemberData.address_1.value
            if ($MemberData.address_2.value)
            {
                $StreetAddress += " " + $MemberData.address_2.value
            }
            if ($MemberData.address_3.value)
            {
                $StreetAddress += " " + $MemberData.address_3.value
            }

            if ([string]::IsNullOrEmpty($StreetAddress))
            {
                $StreetAddress = ""
            }

            if ($StreetAddress -notlike $O365MemberData.StreetAddress)
            {
                $updateUserparams["StreetAddress"] = $StreetAddress
            }

            if ($MemberData.postcode.value -notlike $O365MemberData.PostalCode)
            {
                $updateUserparams["PostalCode"] = $MemberData.postcode.value
            }

            if ($MemberData.town.value -notlike $O365MemberData.City)
            {
                $updateUserparams["City"] = $MemberData.town.value
            }

            if ($MemberData.country.value -notlike $O365MemberData.Country)
            {
                $updateUserparams["Country"] = $MemberData.country.value
            }
#endregion

#region User name
            if ("$($MemberData.first_name.value) $($MemberData.last_name.value)" -notlike $O365MemberData.DisplayName)
            {
                $updateUserparams["DisplayName"] = "$($MemberData.first_name.value) $($MemberData.last_name.value)"
            }

            if ($MemberData.first_name.value -notlike $O365MemberData.GivenName)
            {
                $updateUserparams["GivenName"] = $MemberData.first_name.value
            }

            if ($MemberData.last_name.value -notlike $O365MemberData.Surname)
            {
                $updateUserparams["Surname"] = $MemberData.last_name.value
            }
#endregion

#region Handle updated MobilePhone number.
            if (-not [string]::IsNullOrEmpty($MemberData.contact_mobile_phone.value))
            {
                if ($MemberData.contact_mobile_phone.value -notlike $O365MemberData.MobilePhone)
                {
                    $updateUserparams["MobilePhone"] = $MemberData.contact_mobile_phone.value
                }
            }
#endregion

#region Handle other mails, used for password reset.
            $OtherMails = New-Object System.Collections.ArrayList
            $userEmailInScoutnet = $null
            if (-not [string]::IsNullOrEmpty($MemberData.email.value))
            {
                if ($AccountData.UserPrincipalName -notlike $MemberData.email.value)
                {
                    # Save the value so both can be set.
                    $userEmailInScoutnet = $MemberData.email.value
                    if (-not $O365MemberData.OtherMails.Contains($MemberData.email.value))
                    {
                        # email usable.
                        [void]$OtherMails.Add($MemberData.email.value)
                    }
                }
            }

            if (-not [string]::IsNullOrEmpty($MemberData.contact_alt_email.value))
            {
                if ($AccountData.UserPrincipalName -notlike $MemberData.contact_alt_email.value)
                {
                    if (-not $O365MemberData.OtherMails.Contains($MemberData.contact_alt_email.value))
                    {
                        # contact_alt_email usable.
                        [void]$OtherMails.Add($MemberData.contact_alt_email.value)
                        if (-not [string]::IsNullOrEmpty($userEmailInScoutnet))
                        {
                            if (-not $OtherMails.Contains($userEmailInScoutnet))
                            {
                                # Add userEmailInScoutnet so both can be set.
                                [void]$OtherMails.Add($userEmailInScoutnet)
                            }
                        }
                    }
                }
            }

            if ($OtherMails.Count -gt 0)
            {
                $updateUserparams["OtherMails"] = $OtherMails.ToArray()
            }
#endregion

            if ($updateUserparams.Count -gt 0)
            {
                # Update user data.
                Update-MgUser -UserId $AccountData.ExternalDirectoryObjectId -BodyParameter $updateUserparams -ErrorAction Stop
                Write-SNSLog "User '$($AccountData.name)' uppdated with new info from Scoutnet."
            }
        }
        else
        {
            Write-SNSLog -Level "Warn" "Could not update user $($AccountData.name). Error user not found in scoutnet data."
        }
    }
    catch
    {
        Write-SNSLog -Level "Warn" "Could not update user $($AccountData.name). Error $_"
    }
}

function Invoke-SNSDisableAccount
{
    <#
    .SYNOPSIS
        Disables the account and moves the user to SNSSecurityGroupScoutnetDisabledUsers.

    .DESCRIPTION
        The account is disabled and the user cannot login. The user is also moved
        to SNSSecurityGroupScoutnetDisabledUsers, and removed from SNSAllUsersGroup.
        Any mail forward is disabled.
        If the setting DisabledAccountsAutoReplyText contains any message, the message
        is set as autoreply message.

        No data is deleted and the licens is still in use.

    .INPUTS
        None. You cannot pipe objects to Get-SNSDisableAccount.

    .OUTPUTS
        None.
    #>

    param (
        [Parameter(Mandatory=$false, HelpMessage="User to disable.")]
        [ValidateNotNull()]
        $AccountData
    )

    try
    {
        Write-SNSLog "Disabling user '$($AccountData.name)' with Id '$($AccountData.ExternalDirectoryObjectId)'"
        Update-MgUser -UserId $AccountData.ExternalDirectoryObjectId -AccountEnabled:$false -ErrorAction "Stop"

        # Mark the account as hidden so the user is not shown in the global address book.
        # Remove any forwarders enabled by the user.
        Set-Mailbox -Identity $AccountData.UserPrincipalName -HiddenFromAddressListsEnabled $True -ForwardingAddress $null -ForwardingSmtpAddress $null -DeliverToMailboxAndForward $false -ErrorAction "Stop"

        # Remove the user from the group of active users.
        $SecurityGroupScoutnet = Get-SNSSecurityGroupScoutnet
        Remove-MgGroupMemberByRef -GroupId $SecurityGroupScoutnet.Id -DirectoryObjectId $AccountData.ExternalDirectoryObjectId -ErrorAction "Stop"

        # Add the user to the group of disabled users.
        $SNSSecurityGroupScoutnetDisabledUsers = Get-SNSSecurityGroupScoutnetDisabledUsers
        New-MgGroupMember -GroupId $SNSSecurityGroupScoutnetDisabledUsers.Id -DirectoryObjectId $AccountData.ExternalDirectoryObjectId -ErrorAction "Stop"

        if ($Script:SNSConf.AllUsersGroupName)
        {
            try
            {
                # Remove the user from the Distribution Group for all users with office 365 account.
                Remove-DistributionGroupMember -Identity $Script:SNSConf.AllUsersGroupName -Member $AccountData.Identity -Confirm:$false -ErrorAction "Stop"
            }
            Catch
            {
                if ($_.CategoryInfo.Reason -ne "MemberAlreadyExistsException")
                {
                    Write-SNSLog -Level "Warn" "Could not remove contact $($AccountData.Identity) from group $($Script:SNSConf.AllUsersGroupName). Error $_"
                }
            }
        }

        if (![string]::IsNullOrWhiteSpace($Script:SNSConf.DisabledAccountsAutoReplyText))
        {
            $DisabledAccountsAutoReplyText = $Script:SNSConf.DisabledAccountsAutoReplyText -Replace "<DisplayName>", $AccountData.DisplayName
            try
            {
                Set-MailboxAutoReplyConfiguration -Identity $AccountData.UserPrincipalName -AutoReplyState Enabled -ExternalAudience All -InternalMessage $DisabledAccountsAutoReplyText -ExternalMessage $DisabledAccountsAutoReplyText
            }
            catch
            {
                Write-SNSLog -Level "Error" "Could not set autoreply message for user $($AccountData.name). Error $_"
            }
        }
    }
    catch
    {
        Write-SNSLog -Level "Error" "Could not disable user $($AccountData.name). Error $_"
    }
}

function Invoke-SNSEnableAccount
{
    <#
    .SYNOPSIS
        Enables the account and moves the user to SNSSecurityGroupScoutnet.

    .DESCRIPTION
        The account is enabled and the user login. The user is also moved
        to SNSSecurityGroupScoutnet, and added to SNSAllUsersGroup.
        Any autoreply is disabled.

        This is only valid for existing accounts.

    .INPUTS
        None. You cannot pipe objects to Get-SNSEnableAccount.

    .OUTPUTS
        None.
    #>

    param (
        [Parameter(Mandatory=$false, HelpMessage="User to enable.")]
        [ValidateNotNull()]
        $AccountData
    )

    try
    {
        Write-SNSLog "Enabling user '$($AccountData.name)' with Id '$($AccountData.ExternalDirectoryObjectId)'"
        Update-MgUser -UserId $AccountData.ExternalDirectoryObjectId -AccountEnabled:$true -ErrorAction "Stop"

        # Add the user to the global address book.
        Set-Mailbox -Identity $AccountData.UserPrincipalName -HiddenFromAddressListsEnabled $false -ErrorAction "Stop"

        # Remove the user from the group of disabled users.
        $SNSSecurityGroupScoutnetDisabledUsers = Get-SNSSecurityGroupScoutnetDisabledUsers
        Remove-MgGroupMemberByRef -GroupId $SNSSecurityGroupScoutnetDisabledUsers.Id -DirectoryObjectId $AccountData.ExternalDirectoryObjectId -ErrorAction "Stop"

        # Add the user to the group of active users.
        $SecurityGroupScoutnet = Get-SNSSecurityGroupScoutnet
        New-MgGroupMember -GroupId $SecurityGroupScoutnet.Id -DirectoryObjectId $AccountData.ExternalDirectoryObjectId -ErrorAction "Stop"

        if ($Script:SNSConf.AllUsersGroupName)
        {
            # Add the user to the Distribution Group for all users with office 365 account.
            Add-DistributionGroupMember -Identity $Script:SNSConf.AllUsersGroupName -Member $AccountData.Identity -ErrorAction "Stop"
        }

        try
        {
            Set-MailboxAutoReplyConfiguration -Identity $AccountData.UserPrincipalName -AutoReplyState Disabled -InternalMessage $null -ExternalMessage $null
        }
        catch
        {
            Write-SNSLog -Level "Error" "Could not disable the autoreply message for user $($AccountData.name). Error $_"
        }
    }
    catch
    {
        Write-SNSLog -Level "Error" "Could not enable user $($AccountData.name). Error $_"
    }
}

function Get-SNSSoutnetLeaders
{
    [OutputType([System.Collections.ArrayList])]
    param (
        [Parameter(Mandatory=$False, HelpMessage="Credentials for api/group/customlists.")]
        [ValidateNotNull()]
        [pscredential]$CredentialCustomlists,

        [Parameter(Mandatory=$False, HelpMessage="Credentials for api/group/memberlist.")]
        [ValidateNotNull()]
        [pscredential]$CredentialMemberlist,

        [Parameter(Mandatory=$False, HelpMessage="Maillist to process.")]
        $MailListId
    )

    $GroupMemberlist = Get-SNSApiGroupMemberlist -Credential $CredentialMemberlist
    $MemberListScoutnet = [System.Collections.ArrayList]::new()
    # Fetch members from scoutnet from the selected maillists.
    foreach($ListId in $MailListId)
    {
        $SelectedList = Get-SNSApiGroupCustomlist -Credential $CredentialCustomlists -listid $ListId
        $SelectedList.data.keys | ForEach-Object {[void]$MemberListScoutnet.Add($_)}
    }

    if ($MemberListScoutnet.Count -eq 0)
    {
        # No members found in list or list not specified.
        # Select users with a role in group or unit and add to MemberListScoutnet
        foreach ($user in $GroupMemberlist.data.values)
        {
            if ( ![string]::IsNullOrWhiteSpace($user.group_role.value) -or ![string]::IsNullOrWhiteSpace($user.unit_role.value))
            {
                Write-SNSLog "Found member '$($user.first_name.value) $($user.last_name.value)' ($($user.member_no.value)) with roles '$($user.group_role.value)', '$($user.unit_role.value)'"
                [void]$MemberListScoutnet.Add($user.member_no.value)
            }
        }
    }

    return $MemberListScoutnet
}

function Get-SNSUsersInSecurityGroupScoutnet
{
    [OutputType([System.Collections.ArrayList])]
    param (
        [Parameter(Mandatory=$False, HelpMessage="List of office 365 users.")]
        [ValidateNotNull()]
        $allOffice365Users
    )
    return Get-SNSUsersInSecurityGroup -allOffice365Users $allOffice365Users -Name $Script:SNSConf.SyncGroupName -Description $Script:SNSConf.SyncGroupDescription
}

function Get-SNSUsersInSecurityGroupScoutnetDisabledUsers
{
    [OutputType([System.Collections.ArrayList])]
    param (
        [Parameter(Mandatory=$False, HelpMessage="List of office 365 users.")]
        [ValidateNotNull()]
        $allOffice365Users
    )
    return Get-SNSUsersInSecurityGroup -allOffice365Users $allOffice365Users -Name $Script:SNSConf.SyncGroupDisabledUsersName -Description $Script:SNSConf.SyncGroupDisabledUsersDescription
}

function Get-SNSSecurityGroupScoutnet
{
    return Get-SNSSecurityGroup -Name $Script:SNSConf.SyncGroupName -Description $Script:SNSConf.SyncGroupDescription
}

function Get-SNSSecurityGroupScoutnetDisabledUsers
{
    return Get-SNSSecurityGroup -Name $Script:SNSConf.SyncGroupDisabledUsersName -Description $Script:SNSConf.SyncGroupDisabledUsersDescription
}

function Get-SNSUsersInSecurityGroup
{
    [OutputType([System.Collections.ArrayList])]
    param (
        [Parameter(Mandatory=$False, HelpMessage="Group name")]
        [ValidateNotNull()]
        [string]$Name,

        [Parameter(Mandatory=$False, HelpMessage="Group description.")]
        [ValidateNotNull()]
        [string]$Description,

        [Parameter(Mandatory=$False, HelpMessage="List of office 365 users.")]
        [ValidateNotNull()]
        $allOffice365Users
    )

    $securityGroup = Get-SNSSecurityGroup -Name $Name -Description $Description

    $securityGroupMembers = [System.Collections.ArrayList]::new()
    if ($securityGroup)
    {
        $GroupMembers = Get-MgGroupMember -GroupId $securityGroup.Id
        # Get the mailbox info for each group member.
        foreach ($GroupMember in $GroupMembers)
        {
            $member = $allOffice365Users | Where-Object -FilterScript {$_.ExternalDirectoryObjectId -eq $GroupMember.Id}
            if ($member)
            {
                [void]$securityGroupMembers.Add($member)
            }
        }
    }

    return $securityGroupMembers
}

function Get-SNSSecurityGroup
{
    [OutputType([System.Collections.ArrayList])]
    param (
        [Parameter(Mandatory=$False, HelpMessage="Group name")]
        [ValidateNotNull()]
        [string]$Name,

        [Parameter(Mandatory=$False, HelpMessage="Group description.")]
        [ValidateNotNull()]
        [string]$Description

    )

    $securityGroups = Get-MgGroup -Search "displayName:$Name" -ConsistencyLevel eventual -ErrorAction "Stop"

    $securityGroup=$null
    foreach($group in $securityGroups)
    {
        if ($group.DisplayName -like $Name)
        {
            $securityGroup = $group
            break
        }
    }

    if (!$securityGroup)
    {
        Write-SNSLog -Level "Warn" "Security group $name is not found. Creating the group."

        $params = @{
            Description = $Description
            DisplayName = $Name
            GroupTypes = @(
            )
            MailEnabled = $false
            MailNickname = $Name
            SecurityEnabled = $true
        }

        $securityGroup = New-MgGroup -BodyParameter $params -ErrorAction "Stop"
    }

    return $securityGroup
}