Exchange Online - Get-UserPhoto - how to catch non terminating error?
Asked Answered
C

2

3

I'm trying to export a list of all users with no photo from our Exchange Online account using powershell. I cannot get it to work and have tried various methods.

Get-UserPhoto returns this exception when there is no profile present.

Microsoft.Exchange.Data.Storage.UserPhotoNotFoundException: There is no photo stored here.

First of all I tried use Errorvariable against the command but received:

A variable that cannot be referenced in restricted language mode or a Data section is being referenced. Variables that can be referenced include the following: $PSCulture, $PSUICulture, $true, $false, and  $null.
+ CategoryInfo          : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : VariableReferenceNotSupportedInDataSection
+ PSComputerName        : outlook.office365.com

Next I tried try, catch but the non-terminating error never calls the catch despite various methods followed online about setting $ErrorActionPreference first of all.

Any ideas ? Here is the script:

 $UserCredential = Get-Credential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection
Import-PSSession $Session 

$timestamp = $timestamp = get-date -Format "dd/M/yy-hh-mm"
$outfile = "c:\temp\" + $timestamp + "UserswithoutPhotos.txt"




$resultslist=@()
$userlist = get-user -ResultSize unlimited -RecipientTypeDetails usermailbox | where {$_.accountdisabled -ne $True}


Foreach($user in $userlist)
{
    try
    {

        $user | get-userphoto -erroraction stop 
    }
    catch
    {
        Write-Host "ERROR: $_"
        $email= $user.userprincipalname
        $name = $user.DisplayName
        $office = $user.office
        write-host "User photo not found...adding to list : $name , $email, $office" 
        $resultslist += $user

    }
}
$resultslist | add-content $outfile 
$resultslist
Cingulum answered 11/5, 2018 at 19:11 Comment(7)
In your try block, try adding $local:ErrorActionPreference = 'Stop'Perce
According to the documentation the cmdlet should support common parameters. I believe even non-terminating errors get added to the $Error variable, though, so you should be able to do: $Error.Clear(); command here; if ($Error) { }Perce
@TheIncorrigible1: A local $ErrorActionPreference preference variable instance is unlikely to help, if common parameter -ErrorAction Stop doesn't work (also, the local: prefix wouldn't make a difference). Yes, non-terminating errors are reflected in $Error too, as, in fact, are all errors - except if common parameter -ErrorAction Ignore is passed to a cmdlet invocation (note, however, that 'Ignore' cannot be set as the value of $ErrorActionPreference preference variable).Unfit
@Unfit I try to avoid touching global preference variables when I can help it (in reference to using $local: scope). I use that trick when calling methods usually so they throw errors in their try block, but don't impact the rest of the script.Perce
@TheIncorrigible1: Re global scope: that's sensible - that's why in my answer I restore the previous value in a finally block - however, setting it at least temporarily is needed (so I'm speculating) to get around script-module variable-scoping issues in this case. Do note that try doesn't create a separate scope - a $local:ErrorActionPreference = 'Stop' inside a try block still stays in effect for the rest of the enclosing function / script.Unfit
@Unfit I had thought try creates its own block scope (due to using a scriptblock syntax). Good to know, thanks!Perce
for anyone else attempting this , it turns out that Get-Mailbox has a HasPicture attribute - much faster to query this than check for exceptions.Cingulum
U
4

PowerShell's error handling is notoriously tricky, especially so with cmdlets that use implicit remoting via a locally generated proxy script module.

The following idiom provides a workaround based on temporarily setting $ErrorActionPreference to Stop globally (of course, you may move the code for restoring the previous value outside the foreach loop), which ensures that even functions from different modules see it:

try {

  # Temporarily set $ErrorActionPreference to 'Stop' *globally*
  $prevErrorActionPreference = $global:ErrorActionPreference
  $global:ErrorActionPreference = 'Stop'

  $user | get-userphoto

} catch {

    Write-Host "ERROR: $_"
    $email= $user.userprincipalname
    $name = $user.DisplayName
    $office = $user.office
    write-host "User photo not found...adding to list : $name, $email, $office" 
    $resultslist += $user

} finally {  

  # Restore the previous global $ErrorActionPreference value
  $global:ErrorActionPreference = $prevErrorActionPreference

}

As for why this is necessary:

  • Functions defined in modules do not see the caller's preference variables - unlike cmdlets, which do.

  • The only outside scope that functions in modules see is the global scope.

For more information on this fundamental problem, see GitHub issue #4568.

Unfit answered 11/5, 2018 at 19:37 Comment(0)
H
1

You can throw your own error like so:

try {
    $error.Clear()
    $user | Get-UserPhoto 
    if ($error[0].CategoryInfo.Reason -eq "UserPhotoNotFoundException") {throw "UserPhotoNotFoundException" }
} catch {
    #code
}
Headwaiter answered 11/5, 2018 at 19:25 Comment(2)
That should work. However, so as not to wipe out the session's $Error collection, perhaps it's better to compare the entry count after with the one before to determine if an error occurred. Also, you can just rethrow the (wrapped) exception (with all metadata) with Throw $Error[0].Unfit
I couldn't get this one to work , it wasn't catching . Thanks for your efforts in helping me thoughCingulum

© 2022 - 2024 — McMap. All rights reserved.