GraphAPI – Find Messages in a Mailbox in specific folders via PowerShell

Scenario: You want to pull a list of messages using GraphAPI via PowerShell for a specific Mailbox.

Prerequisites: You already have the following configured:
Registered App in Azure: An Azure Registered app to connect to with Application Permissions for Mail.Read, Mail.ReadWrite, Mail.ReadBasic.All, and Mail.Send. (Not all permissions listed may be necessary for the specific function below)
Bearer Authentication Token: A method for pulling back a bearer token and storing it to pass Authorization into your RestAPI package (See my previous post about get-accesstoken). The get-accesstoken authenticates against your Registered App in Azure.

Scriptlets in PowerShell:

#1. Define $mbx
$mbx = "steve@steveman.com"

#2. Build your $AppURI (Examples)
$appuri = "https://graph.microsoft.com/v1.0/users/$mbx/mailfolders/inbox/Messages"   #using common folder names
 
$appuri = "https://graph.microsoft.com/v1.0/users/$mbx/mailfolders/AAMkADY3ODhkMDNlLTRjM2YtNDJjYi04YTkzLTFmNjFhYTcyMjA2NwAuAAAAAAAM5kRh6DylRZ4XsImGGVClAQA8oulgK3qgQ659gFhlXH_UAAAAAAEMAAA=/Messages"  #Using Base64 naming for any folder in mailbox
            
$appuri = "https://graph.microsoft.com/v1.0/users/$mbx/mailfolders('RecoverableItemsDiscoveryHolds')/Messages"
            
$appuri = "https://graph.microsoft.com/v1.0/users/$mbx/mailfolders('RecoverableItemsPurges')/Messages"


#3. Run the following in a Do-While loop so it collects for than 10 messages at a time:
          #Create your Collection Variable
                $messages = @()
            #Run a Do-While Loop
            
                Do{
                    "Running $appUri"
            
                    $RestSplat = @{
                        URI = $appuri
                        Headers = $(get-accesstoken)
                        Method = 'GET'
                        ContentType = "application/json"
                    }
                
                    $Tempresults = Invoke-RestMethod @RestSplat
                    $messages += $tempresults.value
                    $appuri = $tempresults."@odata.NextLink"
                    }While($appuri -ne $null)

          #Display your Messages
                $Messages

Graph API – Pull a list of Folders for a Mailbox via PowerShell

Scenario – You need to pull a list of folders in a mailbox.

Prerequisites: You already have the following configured:
Registered App in Azure: An Azure Registered app to connect to with Application Permissions for Mail.Read, Mail.ReadWrite, Mail.ReadBasic.All, and Mail.Send. (Not all permissions listed may be necessary for the specific function below)
Bearer Authentication Token: A method for pulling back a bearer token and storing it to pass Authorization into your RestAPI package (See my previous post about get-accesstoken). The get-accesstoken authenticates against your Registered App in Azure.

PowerShell Scriptlets:

#Examples are below for common Folders and how to build the URI
#Notes: For common folders, you can use the name of the folder. For user created folders, or other non-common folders, you can use the Base64 ID

#1. Define $MBX
$mbx = "steve@steveman.com"                


#2. Build your #APPURI
$appuri = "https://graph.microsoft.com/v1.0/users/$mbx/mailfolders"   #to get a list of folders

$appuri = "https://graph.microsoft.com/v1.0/users/$mbx/mailfolders/inbox"   #using common folder names

$appuri = "https://graph.microsoft.com/v1.0/users/$mbx/mailfolders/AAMkADY3ODhkMDNlLTRjM2YtNDJjYi04YTkzLTFmNjFhYTcyMjA2NwAuAAAAAAAM5kRh6DylRZ4XsImGGVClAQA8oulgK3qgQ659gFhlXH_UAAAAAAEMAAA="  #Using Base64 naming for any folder in mailbox


#3. Build  your $RestSplat
$RestSplat = @{
                        URI = $appuri
                        Headers = $(get-accesstoken)
                        Method = 'GET'
                        ContentType = "application/json"
                    }


#4. Run the RestMethod to pull back the Folder(s)
$Folders = Invoke-RestMethod @RestSplat


#5. Display the Results
$Folders.Value



Note: GraphAPI only pulls back 10 items at a time.  If you need to pull back more than 10 items, run the following Do-While Loop:

#To find all Folders in a mailbox, run this:
                #Create your Collection Variable
                    $Folders = @()
                #Build your Starting URI
                    $appuri = "https://graph.microsoft.com/v1.0/users/$mbx/mailfolders"   
                #Run a Do-While Loop
            
                Do{
                    "Running $appUri"
            
                    $RestSplat = @{
                        URI = $appuri
                        Headers = $(get-accesstoken)
                        Method = 'GET'
                        ContentType = "application/json"
                    }
                
                    $Tempresults = Invoke-RestMethod @RestSplat
                    $Folders += $tempresults.value
                    $appuri = $tempresults."@odata.NextLink"
                    }While($appuri -ne $null)

          #Display your Messages
                $Folders

        

Stuck at “Verify your account” when accessing Outlook Web

Scenario: When accessing Outlook Web in Exchange Online, a user is redirected to a CAPTCHA page asking to ‘Verify your account’. However the Captcha test never fully loads and you are unable to verify to access your mailbox in Outlook Web — it appears stuck.


Solution: Quickly, navigate to https://outlook.office.com/mail/options/calendar/calAccounts and remove any accounts that you may have connected to previously. It was a race before being redirected to the Captcha site. We had a user with a GMAIL account configured, and after removing it the use was unable to access Outlook Web. The user can access Gmail on Gmails site ;).

Error: The request was aborted: Could not create SSL/TLS secure channel

Scenario: You are trying to access or download from a URL within PowerShell and you receive this error message:

The request was aborted: Could not create SSL/TLS secure Channel


Solution: Use TLS1.2 in your PowerShell to initiate the download.

#verifies that TLS1.2 is available within your PowerShell
	[Enum]::GetNames([Net.SecurityProtocolType]) -contains 'Tls12'

#If True, Checks to see if TLS1.2 is in use with PS
	[System.Net.ServicePointManager]::SecurityProtocol.HasFlag([Net.SecurityProtocolType]::Tls12)

#If False Run the following to enable the use of TLS1.2
	[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12

#Check again for True
[System.Net.ServicePointManager]::SecurityProtocol.HasFlag([Net.SecurityProtocolType]::Tls12)

#Then attempt to initate the install
iex (New-Object Net.WebClient).DownloadString("https://gist.github.com/darkoperator/6152630/raw/c67de4f7cd780ba367cccbc2593f38d18ce6df89/instposhsshdev")

 Error:   Remove-ADUser : The directory service can perform the requested operation only on a leaf object. 

Scenario: When cleaning up Active Directory accounts, you receive the following error:

 Remove-ADUser : The directory service can perform the requested operation only on a leaf object. 

Solution: Our issue is caused by mobile devices being attached within these user accounts. Instead for using the Remove-ADUser commandlet, use the Remove-ADObject with a -recursive commandlet to get ad objects, such as mobile devices, that are attached.

Remove-ADObject “CN=buhbye,OU=Disabled,DC=domain,DC=com” -confirm:$false -recursive

 0x800f0954 – When adding AD Tools to your Windows 10 Computer

Scenario: When adding AD Tools to your Windows 10 computer, you receive the error: Add-WindowsCapability failed. Error code = 0x800f0954

Solution:
1. Go to GPEDIT.MSC
2. Computer Configuration –> Administrative Templates –>System
3. Locate: Specify settings for optional component installation and component repair
4. Select Enabled.
5. Run a GPUpdate /force in PowerShell/CommandLine
6. Attempt to add the AD Poweshell tools again: Add-WindowsCapability -Name ‘Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0’ -Online 

0x8024002e when adding AD Tools to your Windows 10 Computer

Scenario: When you try to install the AD Tools to your computer, you receive the error: 0x8024002e.

Solution:
In the registry, navigate to HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\WindowsUpdate and change the Dword value for DisableWindowsUpdateAccess from a 1 to a 0.
After a computer restart, you can attempt to run the install again in PowerShell:

Add-WindowsCapability -Name ‘Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0’ -Online


Hybrid Modern Authentication Checker

Scenario: If you are having issues with Hybrid Modern authentication in your Exchange On-Premises environment, feel free to use some of the logic below in the script.

Script: If Hybrid Modern authentication is already in place, feel free to start with the following:
1. Run the Test-OAUTHConnectivity command (Step 7)
2. Run the Hybrid Config Wizard should put the pieces in place necessary.
3. We had to perform step 3 due to an HMA interactive signin loop.



#Hybrid Modern Authentication Checker
#Steps expanded based on this article: https://docs.microsoft.com/en-us/exchange/configure-oauth-authentication-between-exchange-and-exchange-online-organizations-exchange-2013-help#step-5-register-all-hostname-authorities-for-your-internal-and-external-on-premises-exchange-http-endpoints-with-azure-active-directory

#1. Connect to Exchange On-Premises
        $n = "ACS","EvoSts"
        Get-AuthServer  | Where name -in $n
        #If both $severs exist,  its good!

#2. Make sure the Partner Application is Enabled
        Get-PartnerApplication |  ?{$_.ApplicationIdentifier -eq "00000002-0000-0ff1-ce00-000000000000" -and $_.Realm -eq ""}

#3. Identify the Current Certificate being used in the Get-AuthConfiguration for ServiceName 00000002-0000-0ff1-ce00-000000000000

        #Get Thumbprint
                $thumbprint = (Get-AuthConfig).CurrentCertificateThumbprint
        
        #Check Cert Expiration (NotAfter)
                Get-ExchangeCertificate -Thumbprint $thumbprint | Select Thumbprint, Services, Subject,NotAfter

        #Check to see what certificate is being used in Microsoft Online, Verify with the StartDate/EndDate
                Connect-msolservice 
                Get-MsolServicePrincipalCredential -ServicePrincipalName "00000002-0000-0ff1-ce00-000000000000" -ReturnKeyValues $true |Select Type, KeyID, Startdate,Enddate

        #If we are receiving a Modern Auth Loop, Run the following to export and re-import the certificate:
            #Export from On-Premises
                $thumbprint = (Get-AuthConfig).CurrentCertificateThumbprint
                if((test-path $env:SYSTEMDRIVE\OAuthConfig) -eq $false)
                {
                    md $env:SYSTEMDRIVE\OAuthConfig
                }
                cd $env:SYSTEMDRIVE\OAuthConfig
                $oAuthCert = (dir Cert:\LocalMachine\My) | where {$_.Thumbprint -match $thumbprint}
                $certType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert
                $certBytes = $oAuthCert.Export($certType)
                $CertFile = "$env:SYSTEMDRIVE\OAuthConfig\OAuthCert.cer"
                [System.IO.File]::WriteAllBytes($CertFile, $certBytes)

            #Import into Microsoft Online 
                Connect-MsolService
                $CertFile = "$env:SYSTEMDRIVE\OAuthConfig\OAuthCert.cer"
                $objFSO = New-Object -ComObject Scripting.FileSystemObject
                $CertFile = $objFSO.GetAbsolutePathName($CertFile)
                $cer = New-Object System.Security.Cryptography.X509Certificates.X509Certificate
                $cer.Import($CertFile)
                $binCert = $cer.GetRawCertData()
                $credValue = [System.Convert]::ToBase64String($binCert)
                $ServiceName = "00000002-0000-0ff1-ce00-000000000000"
                $p = Get-MsolServicePrincipal -ServicePrincipalName $ServiceName
                New-MsolServicePrincipalCredential -AppPrincipalId $p.AppPrincipalId -Type asymmetric -Usage Verify -Value $credValue

            #Verify the new certificate is there:
                Connect-MsolService
                Get-MsolServicePrincipalCredential -ServicePrincipalName "00000002-0000-0ff1-ce00-000000000000" -ReturnKeyValues $true


#4. SPN Check:
    #Verify the namespaces used in Exchange On-Prmeises are all present and registered as SPNs in Microsoft Online 
        Get-MapiVirtualDirectory | FL server,*url*
        Get-WebServicesVirtualDirectory | FL server,*url*
        Get-ClientAccessServer | fl Name, AutodiscoverServiceInternalUri
        Get-OABVirtualDirectory | FL server,*url*
        Get-AutodiscoverVirtualDirectory | FL server,*url*
        Get-OutlookAnywhere | FL server,*url*

    #Verify the SPNs (urls) are listed in Microsoft for 00000002-0000-0ff1-ce00-000000000000
        Get-MsolServicePrincipal -AppPrincipalId 00000002-0000-0ff1-ce00-000000000000 | select -ExpandProperty ServicePrincipalNames

    #If you need to add a SPN because a new namespace is being used, or if a namespace is missing:
        $ServiceName = "00000002-0000-0ff1-ce00-000000000000";
        $x = Get-MsolServicePrincipal -AppPrincipalId $ServiceName;
        $x.ServicePrincipalnames.Add("https://mail.contoso.com/");
        $x.ServicePrincipalnames.Add("https://autodiscover.contoso.com/");
        Set-MSOLServicePrincipal -AppPrincipalId $ServiceName -ServicePrincipalNames $x.ServicePrincipalNames;


#5. Verify the IntraOrganizationConnector in Exchange On-Premises
            Get-IntraOrganizationConnector

        #If you need to add it:
            $ServiceDomain = Get-AcceptedDomain | where {$_.DomainName -like "*.mail.onmicrosoft.com"} | select -ExpandProperty Name
            New-IntraOrganizationConnector -name ExchangeHybridOnPremisesToOnline -DiscoveryEndpoint https://outlook.office365.com/autodiscover/autodiscover.svc -TargetAddressDomains $ServiceDomain



#6. Verify the IntraOrganizationConnector in Exchange Online
            get-intraorganizationconnector

            #If you need to add it
            New-IntraOrganizationConnector -name ExchangeHybridOnlineToOnPremises -DiscoveryEndpoint <your on-premises Autodiscover endpoint> -TargetAddressDomains <your on-premises SMTP domain>


#7. Test Oauth Connectivity

        #From Exchange Online PowerShell
            test-OAuthConnectivity -Service EWS -TargetUri https://<OnPremNamespace>/metadata/json/1 -Mailbox <ExOnline MBX UPN> -Verbose
        
        #From Exchange On-Premises PowerShell
            test-OAuthConnectivity -Service EWS -TargetUri https://outlook.office365.com/ews/exchange.asmx -Mailbox  -Verbose


Hybrid Modern Authentication Authentication Loop

Scenario: When authenticating to an Exchange On-Premises mailbox with using the interactive login presented by Hybrid Modern Authentication, you get stuck in an endless loop of interactive login attempts:

1. You successfully satisfy the interactive logon
2. The interactive login window disappears as it normally should after a successful login
3. But immediately the interactive windows pops back up for you to do it again.

Solution: In our case, we had to replace the Authentication Certificate that was being used in the MSOLServicePrincipalCredential for Exchange Online. However, lets walk through some of the troubleshooting than into the fix:

1. TEST OAUTH Connectivity
I tested OAUTH Connectivity between Exchange Online and Exchange On-Premises, running these commands:

#From Exchange Online PowerShell
test-OAuthConnectivity -Service EWS -TargetUri https://<OnPremises Exchange Namespace>/metadata/json/1 -Mailbox <ExOnline MBX UPN> -Verbose

#From Exchange On-Premises PowerShell
test-OAuthConnectivity -Service EWS -TargetUri https://outlook.office365.com/ews/exchange.asmx -Mailbox <ExOnline MBX UPN> -Verbose

Both resulted in ResultType: Error with the details of the Error being:

System.net.webexception: The remote server returned an error: (401) Unauthorized. ……. Reason=”The token has an invalid signature.”;error_category=”invalid_signature

2. Hmm, seems like our Hybrid Modern Auth is broke — Bad tokens are being generated. So its time to start digging into the Configure OAUTH authentication between Exchange and Exchange Online Organizations

Now, Hybrid Modern Authentication was already enabled and working at some point in the past in this environment– how long ago? not really sure. So I know most of these components are already there and enabled. To jump straight to the solution that fixed us, it was Step 3 and Step 4,

Step 3: Export the on-premises authorization certificate

$thumbprint = (Get-AuthConfig).CurrentCertificateThumbprint
if((test-path $env:SYSTEMDRIVE\OAuthConfig) -eq $false)
{
   md $env:SYSTEMDRIVE\OAuthConfig
}
cd $env:SYSTEMDRIVE\OAuthConfig
$oAuthCert = (dir Cert:\LocalMachine\My) | where {$_.Thumbprint -match $thumbprint}
$certType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert
$certBytes = $oAuthCert.Export($certType)
$CertFile = "$env:SYSTEMDRIVE\OAuthConfig\OAuthCert.cer"
[System.IO.File]::WriteAllBytes($CertFile, $certBytes)

Step 4: Upload the on-premises authorization certificate to Azure Active Directory Access Control Service (ACS)

Connect-MsolService
$CertFile = "$env:SYSTEMDRIVE\OAuthConfig\OAuthCert.cer"
$objFSO = New-Object -ComObject Scripting.FileSystemObject
$CertFile = $objFSO.GetAbsolutePathName($CertFile)
$cer = New-Object System.Security.Cryptography.X509Certificates.X509Certificate
$cer.Import($CertFile)
$binCert = $cer.GetRawCertData()
$credValue = [System.Convert]::ToBase64String($binCert)
$ServiceName = "00000002-0000-0ff1-ce00-000000000000"
$p = Get-MsolServicePrincipal -ServicePrincipalName $ServiceName
New-MsolServicePrincipalCredential -AppPrincipalId $p.AppPrincipalId -Type asymmetric -Usage Verify -Value $credValue

After running this command, I immediately tested with my email client and Poof — no more interactive logon loop. Test-OAUTHConnectivity was also coming back a success. One more command that I found helpful was verifying the MSOLServicePrincipalCredential:

Get-MsolServicePrincipalCredential -ServicePrincipalName “00000002-0000-0ff1-ce00-000000000000” -ReturnKeyValues $true | Select Type, KeyID, StartDate,EndDate

After I uploaded, and actually did it twice to see if there would be any negative impact (there was not), I saw the new certificate listed. You can easily identify the cert by the StartDate/EndDate of it.

Uploading the new cert fixed it!

AADSTS500011: The resource principal named was not found in the tenant

Scenario: If your receive the following error when using Hybrid Modern Authentication for Exchange OnPremises, run the following scriptlet below. Our problem was a misconfigured URL set on the get-clientaccessserver where it was pointing to a Servername, and not our shared namespace.

Scriptlet:

 Get-MapiVirtualDirectory | FL server,*url*
 Get-WebServicesVirtualDirectory | FL server,*url*
 Get-ClientAccessServer | fl Name, AutodiscoverServiceInternalUri
 Get-OABVirtualDirectory | FL server,*url*
 Get-AutodiscoverVirtualDirectory | FL server,*url*
 Get-OutlookAnywhere | FL server,*url*