GraphAPI – Move messages in a mailbox folder to another folder

Scenario: You want to move messages from one mailbox folder to another

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:

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

#2. Build your $AppURI of the source location of the mailbox folder items
#Multiple Examples:
$appuri = "https://graph.microsoft.com/v1.0/users/$mbx/mailfolders/inbox/Messages"   

$appuri = "https://graph.microsoft.com/v1.0/users/$mbx/mailfolders/AAMkADY3ODhkMDNlLTRjM2YtNDJjYi04YTkzLTFmNjFhYTcyMjA2NwAuAAAAAAAM5kRh6DylRZ4XsImGGVClAQA0fvQp6d6ET6JezCAd8xOWAAFrER8_AAA=/Messages"
                                
$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. Find All messages in that source folder
 $messages = @()
                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 if you want to see
                $Messages

#4. Move the messages you found in the step above to the destination folder
#Build a $Params Variable with the folderID we want to pass to
#You can use a Base64 ID
$params = @{
	                    DestinationId = "AAMkADY3ODhkMDNlLTRjM2YtNDJjYi04YTkzLTFmNjFhYTcyMjA2NwAuAAAAAAAM5kRh6DylRZ4XsImGGVClAQA0fvQp6d6ET6JezCAd8xOWAAFrER8_AAA="
                    } | Convertto-Json

#Or use the name of a common folder 
                    $params = @{
	                    DestinationId = "Inbox"
                    } | Convertto-Json

#Move the Messages
            $c = 0
            $T = $messages.id.count
            $messages.id | %{
                $m = $_
                $c++
                "$c / $t : Moving $M"
                $appuri = "https://graph.microsoft.com/v1.0/users/$mbx/messages/$m/move"
                        $RestSplat = @{ 
                            URI         = $appuri
                            Headers     = $(get-accesstoken)
                            Method      = 'POST' 
                            ContentType = "application/json" 
                            Body = $params 
                            }
                $move = Invoke-RestMethod @RestSplat 
            }
Advertisement

GraphAPI – Creating a Mailbox Folder via PowerShell

Scenario: You want to create mailbox folders using GraphAPI via PowerShell

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:

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

#2. Build your AppURI
$appuri = "https://graph.microsoft.com/v1.0/users/$mbx/mailfolders"

#3. Build your $params to include the new Folder Name and if it should be hidden
                $params = @{
        	         DisplayName = "TestGraphAPI"
	                 IsHidden = $false
                     } | ConvertTo-Json

#4. Build your RestSplat package
#Build your URI Package to POST
                $RestSplat = @{ 
                    URI         = $appuri
                    Headers     = $(get-accesstoken)
                    Method      = 'POST' 
                    ContentType = "application/json" 
                    Body = $params 
                }        

#5. Execute the RestSplat package
Invoke-RestMethod @RestSplat                       

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