Link to home
Start Free TrialLog in
Avatar of PowerShell Newbie
PowerShell Newbie

asked on

Help with Account Creation Script

Hello, I inherited an student account creation script that a previous admin wrote in PowerShell. I'm still learning the basics of PowerShell and need some help.

I would like to be able to increment by 1 ascending up on the usernames if it is not available and use that for all aspects of the account creation sections. So if JMSmith is taken the script will try JMSmith1, then JMSmith2, etc. That way I don't have users with JMSmith2 for their username in active driectory but JMSmith@domain.edu for their email.

Thank you for any help provided.

Here is the format of the CSV I place in the \ToBeProcessed folder.

id_num,first_name,middle_name,last_name,email_address
654321,Jason,M,Smith,darealmccoy@gmail.com

Open in new window


The script right now will try to only use JMSmith.

# =============================================================================================================================================================
#    Student Creation Script
# =============================================================================================================================================================
#    Global Variables used throughout the script
# =============================================================================================================================================================

$DataSourceDir = "\\server1\IT\AccountCreation\ToBeProcessed"                   #This is the directory that the CSVs are dropped into
$DataWorkingDir = "\\server1\IT\AccountCreation\Working"                        #This is the directory that we move the current CSVs to work on
$DataProcessedDir = "\\server1\IT\AccountCreation\ProcessedCSVs"                #After we have processed CSVs, we move the files here
$HelpdeskPassword = "nottherealpassword"                                        #Password for the Admin user on Office365
$LiveSession = $Null                                                            #This will eventually contain the Office365 remote powershell session
$NewStudents = @()                                                              #This will eventually contian an array of all of the new student accounts to create
$FullyCreatedStudents = @()                                                     #This will eventually contain an array of all new student accounts that had both an AD user account and email address made
$PartiallyCreatedStudents = @()                                                 #This will eventually contain an array of student accounts that were only partially created

#Variables for output data email
$OutputDataEmailBody = "Attached are new students email addresses."             #Subject of email containing student data, specifically new email addresses
$OutputDataEmailSub = "New Students Raw Data"
$EmailAddressToSendOutputDataTo = "user@domain.edu"
$ErrorEmailAddress = "user@domain.edu"


# =============================================================================================================================================================
#    Functions
# =============================================================================================================================================================

# =============================================================================================================================================================
#   Import Active Directory module and create Office365 remote powershell session
# =============================================================================================================================================================
function Load-StudentUserCreationEnviroment 
    {
        if (-not (Get-Module ActiveDirectory))
            {
                Import-module ActiveDirectory
            }

        #Load Exchange Server 2010 Management Shell if not loaded. You may delete/comment out this step if you are running the script from the Exchange Management Shell 
        $ExchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://exchangeserver.domain.edu/PowerShell/ -Authentication Kerberos
        Import-PSSession $ExchangeSession -AllowClobber -DisableNameChecking
        
        #Create PSSession for Office365
        $LivePassword = ConvertTo-securestring -Force -AsPlainText $HelpdeskPassword
        $LiveCred = new-object -typename System.Management.Automation.PSCredential -argumentlist "admin@domian.edu",$LivePassword
        New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell/ -Credential $LiveCred -Authentication Basic –AllowRedirection -Name Live -ErrorAction SilentlyContinue
        
        #This changes the LiveSession variable to the Office365 PSSession.  It passes the PSSession object up out of this function
        Set-Variable -Name LiveSession -Value (Get-PSSession -Name Live) -Scope 1
    }

# =============================================================================================================================================================
#   The opposite of Load-StudentUserCreationEnviroment     
# =============================================================================================================================================================
function UnLoad-StudentUserCreationEnviroment 
    {
        if (Get-Module ActiveDirectory)
            {
    
                Remove-module ActiveDirectory
            }

        #Remove Exchange Server 2010 Management Shell if not loaded. You may delete/comment out this step if you are running the script from the Exchange Management Shell 
        Get-PSSession -ComputerName exchangeserver.domain.edu | Remove-PSSession
        
        
        # Remove PSSession for Office365
        $LiveSession | Remove-PSSession -ErrorAction SilentlyContinue
        $LiveSession = $Null
        
    }

# =============================================================================================================================================================
#   Creates the student mailbox    
# =============================================================================================================================================================
function Create-Mailbox ($this_DisplayName, $this_EmailAddress, $this_first_name, $this_last_name, $this_Initial, $this_Password)
    {
        Write-Host "Creating Mailbox for $this_DisplayName" -ForegroundColor blue
        Invoke-Command -Session $LiveSession -ScriptBlock {param ($IvDisplayName,$IvPassword,$IvEmailAddress,$Ivfirst_name,$Ivlast_name,$IvInitial) `
            New-MailBox -Name $IvDisplayName -Password $IvPassword -DisplayName $IvDisplayName -FirstName $Ivfirst_name -LastName $Ivlast_name -MicrosoftOnlineServicesID $IvemailAddress -Initial $IvInitial -ErrorAction SilentlyContinue } `
            -Arg $this_DisplayName,$this_Password,$this_EmailAddress,$this_first_name,$this_last_name,$this_Initial > $results
                
            
        if (Invoke-Command -Session $LiveSession -ScriptBlock {param ($InvokeEm) Get-Mailbox -Identity $InvokeEm } -Arg $this_EmailAddress -ErrorAction SilentlyContinue )
        {
            Write-Host "Mailbox with the email address of $this_EmailAddress has just been successfully created." -ForegroundColor blue
            $exitcode = $True
            $exitcode = $exitcode | Add-Member NoteProperty StatusMessage "Mailbox with the email address of $this_EmailAddress has just been successfully created." -PassThru
        }
        else
        {
            Write-Host "Failed to create mailbox for $this_DisplayName." -ForegroundColor red
            $exitcode = $False
            $exitcode = $exitcode | Add-Member NoteProperty StatusMessage "Failed to create mailbox for $this_DisplayName." -PassThru
        }
        
    }

# =============================================================================================================================================================
#   Verifies student information values in a row in the CSV 
# =============================================================================================================================================================
function Test-ADUserAlreadyExists ($this_id_num, $this_samAccountName, $this_ADuserDisplayName, $this_LegacyDisplayName,  $this_AddressBookDisplayName)
    {
        if (Get-ADUser -filter {(employeeID -eq $this_id_num) -or (employeeNumber -eq $this_id_num)} -ErrorAction SilentlyContinue)
            {
                Write-Host "User  $this_AddressBookDisplayName already has an AD User account with the same ID Number of $this_id_num" -ForegroundColor red
                $solid = $False
                return $solid
            }
        if (Get-ADUser -filter {(samAccountName -eq $this_samAccountName)} -ErrorAction SilentlyContinue)
            {
                Write-Host "User  $this_AddressBookDisplayName already has an AD User account with the same login as $this_samAccountName" -ForegroundColor red
                $solid = $False
                return $solid
            }
        if ((Get-ADUser -Filter {(displayName -eq $this_ADuserDisplayName)} -ErrorAction SilentlyContinue) -or (Get-ADUser -Filter {(displayName -eq $this_LegacyDisplayName)} -ErrorAction SilentlyContinue))
            {
                Write-Host "User  $this_AddressBookDisplayName already has a AD User account with the same name as $this_ADuserDisplayName or $this_LegacyDisplayName" -ForegroundColor red
                $solid = $False
                return $solid
            }
        
        $solid = $True
        return $solid 
    }

# =============================================================================================================================================================    
#   Verifies student information values in a row in the CSV 
# =============================================================================================================================================================
function Test-ADContactAlreadyExists ($this_ADuserDisplayName, $this_AddressBookDisplayName, $this_EmailAddress)
    {
        if (Get-MailContact -Identity $this_EmailAddress -ErrorAction SilentlyContinue)            
            {
                Write-Host "User $this_AddressBookDisplayName already has an AD Contact with the same email address of $this_EmailAddress" -ForegroundColor red
                $solid = $False
                return $solid
            }
        if ((Get-MailContact -Filter {(displayName -eq $this_ADuserDisplayName)} -ErrorAction SilentlyContinue) -or (Get-MailContact -Filter {(displayName -eq $this_AddressBookDisplayName)} -ErrorAction SilentlyContinue))
            {
                Write-Host "User $this_AddressBookDisplayName already has an AD Contact with the same name as $this_ADuserDisplayName or $this_AddressBookDisplayName" -ForegroundColor red
                $solid = $False
                return $solid
            }
        
        $solid = $True
        return $solid
         
    }
# =============================================================================================================================================================    
#   Verifies student information values in a row in the CSV 
# =============================================================================================================================================================
function Test-MailboxAlreadyExists ($this_DisplayName, $this_EmailAddress)
    {
       if (Invoke-Command -Session $LiveSession -ScriptBlock {param ($InvokeEm) Get-MailContact -Identity $InvokeEm -ErrorAction SilentlyContinue } -Arg $this_EmailAddress -ErrorAction SilentlyContinue )
        {
            Write-Host "A contact with the email address $this_EmailAddress already exists.  Verify that this is not a Faculty or Staff user." -ForegroundColor red
            $solid = $False
            return $solid
        }
        if (Invoke-Command -Session $LiveSession -ScriptBlock {param ($InvokeDisName) Get-MailContact -Identity $InvokeDisName -ErrorAction SilentlyContinue} -Arg $this_DisplayName -ErrorAction SilentlyContinue )
        {
            Write-Host "A contact with the $this_DisplayName already exists. Verify that this is not a Faculty or Staff user." -ForegroundColor red
            $solid = $False
            return $solid
        }
        if (Invoke-Command -Session $LiveSession -ScriptBlock {param ($InvokeEm) Get-Mailbox -Identity $InvokeEm -ErrorAction SilentlyContinue} -Arg $this_EmailAddress -ErrorAction SilentlyContinue )
        {
            Write-Host "A mailbox with the email address of $this_EmailAddress already exists." -ForegroundColor red
            $solid = $False
            return $solid
        }
        if (Invoke-Command -Session $LiveSession -ScriptBlock {param ($InvokeDisName) Get-Mailbox -Identity $InvokeDisName -ErrorAction SilentlyContinue} -Arg $this_DisplayName -ErrorAction SilentlyContinue)
        {
            Write-Host "A mailbox with the Name of $this_DisplayName already exists." -ForegroundColor red
            $solid = $False
            return $solid
        }
        
        $solid = $True
        return $solid 
    }        

# =============================================================================================================================================================
#    Import working CSV files and output each new user object
# =============================================================================================================================================================
function Get-NewStudent   ($DataSourceDir, $DataWorkingDir)
    {
        #  Move files from the dump dir to the working dir
        dir $DataSourceDir -Filter *.csv | mv -Destination $DataWorkingDir
        
        #  Import all unique Students
        $NewTradStudents = dir $DataWorkingDir -Filter Trad*.csv | Import-CSV  | sort id_num –Unique
        $NewGradStudents = dir $DataWorkingDir -Filter Grad*.csv | Import-CSV  | sort id_num –Unique

        $AllUsersRaw = @()
        
        # Add their status as a new property
        
        foreach ($user in $NewTradStudents)
        {
            if (Get-GoodCSVLine $user)
            {
                $user | Add-Member NoteProperty Status "TRADStudent"
                $user | Add-Member NoteProperty userAccountOU "OU=Students,DC=domain,DC=edu"
                $user | Add-Member NoteProperty contactAccountOU "TRADStudent"
                
                $AllUsersRaw = $AllUsersRaw + $user
            }
        }
        
        foreach ($user in $NewGradStudents)
        {
            if (Get-GoodCSVLine $user)
            {
                $user | Add-Member NoteProperty Status "GRADStudent"
                $user | Add-Member NoteProperty userAccountOU "OU=GPS,OU=Students,DC=domain,DC=edu"
                $user | Add-Member NoteProperty contactAccountOU "GASStudent"
                
                $AllUsersRaw = $AllUsersRaw + $user
            }
        }
        
        #  Add some new fields that will eventually be generated
        foreach ($user in $AllUsersRaw)
        {
            $EachNewUser = New-Object System.Management.Automation.PsObject
            $EachNewUser = $user
            $EachNewUser | Add-Member NoteProperty samAccountName $Null
            $EachNewUser | Add-Member NoteProperty emailAddress $Null
            $EachNewUser | Add-Member NoteProperty UPN $Null
            $EachNewUser | Add-Member NoteProperty AddressBookDisplayName $Null
            $EachNewUser | Add-Member NoteProperty ADuserDisplayName $Null
            $EachNewUser | Add-Member NoteProperty LegacyDisplayName $Null
            $EachNewUser | Add-Member NoteProperty HomeDirectory $Null
            $EachNewUser | Add-Member NoteProperty NewPassword $Null
            $EachNewUser | Add-Member NoteProperty PasswordShortBy $Null
            
            #Create and make sure the Student ID number is as least 6 characters long for Email password
            
            if ($EachNewUser.id_num.length -lt 6)
            {
                $EachNewUser.NewPassword = $EachNewUser.id_num
                $numberLess = (6 - $EachNewUser.id_num.length)
                $EachNewUser.PasswordShortBy = $numberLess
                for($i=0; $i -lt $numberLess; $i++) 
                {
                    $EachNewUser.NewPassword += "0"
                }
            }
            else
            {
                $EachNewUser.NewPassword = $EachNewUser.id_num
            }    
               
            #$last_nameWithoutPunc = $Null
            $last_nameWithoutPunc = $Null
                
            # Create middle initial if there isn't one
            if (!($EachNewUser.middle_name))
            {
                $EachNewUser.middle_name = "X"
            } 
                
            #Get the users last name without punctuation and the right length
            $last_nameWithoutPunc =[System.Text.RegularExpressions.Regex]::Replace($EachNewUser.last_name,"[^1-9a-zA-Z_]","");
               
            #Create the SAMAccountName of the right length
            $EachNewUser.samAccountName = $EachNewUser.first_name.Substring(0,1) + $EachNewUser.middle_name.Substring(0,1) + $last_nameWithoutPunc 
            if ($($EachNewUser.samAccountName).length -gt 20){$EachNewUser.samAccountName = $($EachNewUser.samAccountName).Substring(0,20)}
               
            #All of the different Display Names
            $EachNewUser.AddressBookDisplayName = $EachNewUser.first_name + " " + $EachNewUser.middle_name.Substring(0,1) + "." + " " + $EachNewUser.last_name
            $EachNewUser.ADuserDisplayName = $EachNewUser.last_name + ", " + $EachNewUser.first_name + " " + $EachNewUser.middle_name.Substring(0,1) + "."
            $EachNewUser.LegacyDisplayName = $EachNewUser.first_name + " " + $EachNewUser.last_name
    
            #Polish off the remaining fields
            $EachNewUser.UPN = $EachNewUser.samAccountName + "@domain.edu"
            $EachNewUser.HomeDirectory = "\\server1\Students\" + $EachNewUser.samAccountName
                   
            #Create email address
            $EachNewUser.emailAddress = $EachNewUser.samAccountName + "@domain.edu"
    
            $EachNewUser            
            
        }
    }

# =============================================================================================================================================================
#    Create Home Directory
# =============================================================================================================================================================        
function Create-UserHomeDirectory 
    {
        param
        (
            [Parameter(Mandatory=$true)]
            $username,
            $permissions = 'F'
        )

        $folderpath = "\\server1\Students\$username"

        # Create Folder:
        if ((Test-Path $folderpath) -eq $false) 
        {
            md $folderpath | Out-Null
            
            # Add NTFS permissions using icacls.exe
            $result = icacls.exe ('"{0}" /grant:r {1}:(OI)(CI){2} ' -f $folderpath, $username, $permissions) 2>&1
            
            # Check status and report errors
            $exitcode = ($LASTEXITCODE -eq 0)
            $exitcode = $exitcode | Add-Member NoteProperty StatusMessage "Creating home folder $($folderpath)" -PassThru
            $exitcode | Add-Member NoteProperty ExitCode $LASTEXITCODE -PassThru
            
            Write-Host "Creating home folder $($folderpath) " -ForegroundColor blue
        }

        else
        {
            $exitcode = $False
            $exitcode = $exitcode | Add-Member NoteProperty StatusMessage "Folder $($folderpath) already existed." -PassThru
            Write-Host "Folder $($folderpath) already existed." -ForegroundColor red
        }
        
    }
   

# =============================================================================================================================================================
#    Sends emails from AccountCreator@domain.edu
# =============================================================================================================================================================
function SendEmail

    {
        Param ($To, $Subject, $Body, $Attach)

        $msg = New-Object Net.Mail.MailMessage
        $msg.From = "AccountCreator@domain.edu"
        $msg.To.Add($To)
        $msg.Body = $Body
        $msg.Subject = $Subject
        
        #Parse File Array and attach each file
        if ($Attach)
        {
            foreach ($FileToAttach in $Attach)
            {
                $att = New-Object Net.Mail.Attachment($FileToAttach.fullname)
                $msg.Attachments.Add($att)

                #Error check if required
                # Write-Output "Have attached an attachment"

            }
        }            

        $client = New-Object net.Mail.SmtpClient("mail.domain.edu")
        $client.Send($msg)

        #Error Check if required
        #Write-Output "E-mail message sent"

    }


# =============================================================================================================================================================
#    Check for blank fields in imported data
# =============================================================================================================================================================
function Get-GoodCSVLine ($line)
    {  
                
        if (!($line.first_name))
            {
                Write-Host "There is no first name specified, possibly a blank line." -ForegroundColor red
                $solid = $False
                return $solid
            }
           
        if (!($line.last_name))
            {
                Write-Host "There is no last name specified, possibly a blank line." -ForegroundColor red
                $solid = $False
                return $solid
            }
             
        if (!($line.id_num))
            {
                Write-Host "There is no student ID specified in $($line.first_name) $($line.last_name) ." -ForegroundColor red
                $solid = $False
                return $solid
            } 
            
        # Everything checked out    
        $solid = $True
        return $solid
    }


# =============================================================================================================================================================
#    Begin Execution of script
# =============================================================================================================================================================

#Load files to begin processing only if new files in the directory
if ((Test-Path ($DataSourceDir + "\*.csv")) -or (Test-Path ($DataWorkingDir + "\*.csv")))
{
    Load-StudentUserCreationEnviroment   
    
    $NewStudents = Get-NewStudent $DataSourceDir $DataWorkingDir
    
    if ($NewStudents)
    {   
        $i = 0
        
        foreach($user in $NewStudents)
        {
            #Visual progress bar if you are running the script manually
            if ($NewStudents.count)
            {
                $i = $i + 1
                Write-Progress -Activity "Percent New Users Processed" -status "New User $i" -percentComplete ($i / $NewStudents.count*100)
            }                
            
            $user | Add-Member NoteProperty CreatedADuser $False
            $user | Add-Member NoteProperty CreatedADcontact $False
            $user | Add-Member NoteProperty CreatedEmail $False
            $user | Add-Member NoteProperty CreatedUserHomeDir $False
            $user | Add-Member NoteProperty CreatedUserInfoLetter $False
            
            $user | Add-Member NoteProperty CreatedADuserLogMessage "Did not create AD user account for unknown reason."
            $user | Add-Member NoteProperty CreatedADcontactLogMessage "Did not create AD contact account for unknown reason."
            $user | Add-Member NoteProperty CreatedEmailLogMessage "Did not create Email account for unknown reason."
            $user | Add-Member NoteProperty CreatedUserHomeDirLogMessage "Did not create user's home directory for unknown reason."
            
            #Creating a secure string out of the students ID for the password
                $Pword = ConvertTo-SecureString $user.NewPassword -asplaintext -force
            
            #Test for and maybe creates a AD user
            if (Test-ADUserAlreadyExists $($user.id_num) $($user.samAccountName) $($user.ADuserDisplayName) $($user.LegacyDisplayName) $( $user.AddressBookDisplayName))
            {
                Write-Host "Creating AD User $($user.samAccountName)" -ForegroundColor blue
                
                
                
                New-ADUser -Name  $($user.samAccountName) `
                    -Path $($user.userAccountOU) `
                    -DisplayName $($user.ADuserDisplayName) `
                    -UserPrincipalName $($user.UPN) `
                    -GivenName $($user.first_name) `
                    -Surname $($user.last_name) `
                    -OtherName $($user.middle_name) `
                    -Initials $($user.middle_name.Substring(0,1)) `
                    -EmailAddress $($user.emailAddress) `
                    -HomeDrive H: `
                    -HomeDirectory $($user.HomeDirectory) `
                    -PasswordNeverExpires:$true `
                    -EmployeeID $($user.id_num) `
                    -AccountPassword $Pword `
                    -ChangePasswordAtLogon $False `
                    -Enabled $True > $results
                    
                Set-ADUser $($user.samAccountName) -Replace @{Pager = $user.id_num}
                Get-ADUser $($user.samAccountName) | Rename-ADObject -NewName $($user.ADuserDisplayName)
                Add-ADGroupMember -Identity PaperCut -Member $($user.samAccountName)
                Add-ADGroupMember -Identity Student_Group -Member $($user.samAccountName)
                
                $user.CreatedADuser = $True
            }
    
            #Test for and maybe creates a AD contact 
    
            if (Test-ADContactAlreadyExists $($user.ADuserDisplayName) $($user.AddressBookDisplayName) $($user.EmailAddress))
            {
                Write-Host "Creating AD Contact $($user.samAccountName)" -ForegroundColor blue
                New-MailContact -Name $($user.AddressBookDisplayName) `
                -ExternalEmailAddress $($user.emailAddress) `
                -Firstname $($user.first_name) `
                -Lastname $($user.last_name) `
                -OrganizationalUnit $($user.contactAccountOU) `
                -ErrorAction Continue > $results
                
                $user.CreatedADcontact = $True
            }  
                
            #Test for and maybe creates an Mailbox
        
            if (Test-MailboxAlreadyExists $($user.AddressBookDisplayName) $($user.emailAddress))
            {
                Create-Mailbox $($user.AddressBookDisplayName) $($user.emailAddress) $($user.first_name) $($user.last_name) $($user.middle_name.Substring(0,1)) $Pword $($user.DistributionListCustomField)
                
                $user.CreatedEmail = $True
                $user.CreatedEmailLogMessage = "Did not create Email account for unknown reason."
            }
        
            #Test for and maybe create Home Directory
            $rv = Create-UserHomeDirectory $($user.samAccountName)
            if ($rv)
            {
                $user.CreatedUserHomeDir = $True
                $user.CreatedUserHomeDirLogMessage = $rv.StatusMessage
            }
            else
            {
                $user.CreatedUserHomeDirLogMessage = $rv.StatusMessage
            }
        
            #Add user either to $FullyCreatedStudents or $PartiallyCreatedStudents 
            if (($user.CreatedADuser) -and ($user.CreatedADcontact) -and ($user.CreatedEmail) -and ($user.CreatedUserHomeDir))
            {
                $FullyCreatedStudents += $user
            }
            else
            {
                $PartiallyCreatedStudents += $user
            }
       
        }  # End of big for each loop
        
        # Create success logs and arrays
        #$FullyCreatedStudents | ft
        #$PartiallyCreatedStudents |ft
                       
            Write-Host "Beginning to send csv" -ForegroundColor blue
            #Lets send helpdesk the finalized data in a CSV
            $NewStudents | select id_num, first_name, last_name, emailAddress, samAccountName, email_address | Export-Csv -NoTypeInformation -Path $($DataWorkingDir + "\newEmailAddresses.csv")
            $FinishedCSV = dir $($DataWorkingDir + "\newEmailAddresses.csv")
            SendEmail $EmailAddressToSendOutputDataTo $OutputDataEmailSub $OutputDataEmailBody $FinishedCSV
            
            #Clean out Working directory
            #All done, move the procesed files to the $DataProcessed location.  
            #Attaching the files to the email takes a bit so we put the command in a do while loop till its done.

            $attempts = 0       #setting initial number of attempts to 0 for cleaning up working directory
            $again = $true      #default do/while loop test value
            do 
            {
                Remove-Item $($DataWorkingDir + "\newEmailAddresses.csv") -ErrorAction SilentlyContinue
                $Stoploop = $?        #capture the success/failure true/false status
                
                if ($Stoploop -ne $True) { Start-Sleep -s 5; $attempts++ }    #wait 5 seconds if a failure occured and increment to number of attempts
                else {$again = $false}
                
                if ($attempts -eq 50) {$again = $false}    #limiting attempts to 50
            }
            While ($again -eq $true)
            
        }
            
        UnLoad-StudentUserCreationEnviroment 
    
    }
    else { Write-Host "There were no good lines in the CSV files.  They are located in $($DataWorkingDir)." -ForegroundColor red }

Open in new window

SOLUTION
Avatar of J0rtIT
J0rtIT
Flag of Venezuela, Bolivarian Republic of image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial