Author Posts

August 20, 2018 at 3:15 pm

Hi everyone,

I'm trying to export the office365 photos of all users at my organization, but the whole process is during about 3 hours. We've about 500 users.

Have something that I can improve at the code, that can turn the process faster?

$users = Get-ADGroupMember "All Employees" | Select-Object samAccountName

$365Cred = Get-Credential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell/ -Credential $365Cred -Authentication Basic -AllowRedirection
Import-PSSession $Session

foreach ($user in $users){
    $photo = Get-UserPhoto -Identity $($user.SamAccountName) -ErrorAction silentlycontinue
    Set-Content -Value $photo.PictureData "C:\Photos\$($user.samAccountName).jpg" -Encoding byte
     }
}

Remove-PSSession $Session

August 20, 2018 at 4:02 pm

You could attempt to do it as a Workflow so that you can run them them in parallel.

https://docs.microsoft.com/en-us/powershell/module/psworkflow/about/about_foreach-parallel?view=powershell-5.1

Since you are in a session, you may need to get all of the photos and then attempt to create the images in parallel. Not sure how much time it would save because you aren't indicating what portion is taking the time.

Try logic like this:

$users = Get-ADGroupMember "All Employees" | Select-Object samAccountName

$365Cred = Get-Credential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell/ -Credential $365Cred -Authentication Basic -AllowRedirection
Import-PSSession $Session

$photos = foreach ($user in $users){
    Get-UserPhoto -Identity $($user.SamAccountName) -ErrorAction silentlycontinue
}

Remove-PSSession $Session
...

foreach -parallel ($photo in $photos) {
    Set-Content -Value $photo.PictureData "C:\Photos\$($user.samAccountName).jpg" -Encoding byte

}

August 20, 2018 at 9:34 pm

Sorry for the lack of this information, the Get-UserPhoto consumes about 90% of the time. I did not know this statement, so I was reading about but some doubts have arisen. Should the code loke like this?

Workflow Get-HBUserPhotos
{
    param([string[]]$Users)

    $photos = foreach ($user in $users){
    Get-UserPhoto -Identity $($user.SamAccountName) -ErrorAction silentlycontinue
    }
    
    foreach -parallel ($photo in $photos) {
        Set-Content -Value $photo.PictureData "C:\Photos\$($user.samAccountName).jpg" -Encoding byte
    }
}

I appreciate your help!

August 20, 2018 at 10:14 pm

If Get-UserPhoto is where the bottleneck is, I'm not sure the workflow is going to get you any performance benefit because you have to share that remote session for each parallel process. See this:

https://stackoverflow.com/questions/17178404/powershell-workflow-exchange-remoting

August 21, 2018 at 1:05 pm

From my own experience with uploading photos to O365, it is a slow painful process.  The joys of dealing with the cloud 😉

August 22, 2018 at 12:05 am

After several researchs I discover a better way to handle with the slow exports:

param
    (
        [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
        [System.Array]
        $users,
 
        [Parameter(Mandatory=$False)]
        [ValidateSet("96x96","240x240","648x648")]
        $Size="240x240",
 
        [Parameter(Mandatory)]
        $Path,
 
        [Parameter(Mandatory)]
        [pscredential]
        [System.Management.Automation.CredentialAttribute()]
        $Credential
    )
 
process
    {
    foreach($user in $users){
        try
        {
            $Uri = [String]::Concat("https://outlook.office365.com/ews/Exchange.asmx/s/GetUserPhoto?email=",$user.UserPrincipalName,"&size=HR",$Size)
            if(Test-Path -Path $Path)
            {
                $File = [string]::Concat($Path,"\",$user.sAMAccountName,".jpg")
                Invoke-WebRequest -Uri $Uri -Credential $Credential -OutFile $File
            }
            else
            {
                Write-Warning "Please Check the Path"
            }
        }
        catch
        {
            $_.Exception.Message 
        }
    }
}

$users is an array of a Get-ADUSer invocation and $path is the export location.

With this code I could decrement the execution time from 3 hours to 8 minutes.

Reference: http://chen.about-powershell.com/2016/08/import-user-profile-picture-from-exchange-online-using-ews-and-powershell/

Thank you all for the help and ideas!