microsoftgraph / msgraph-sdk-php

Microsoft Graph Library for PHP.
Other
568 stars 144 forks source link

How to convert HexEntryId (coming from Outlook) to EntryId #931

Closed basementmedia2 closed 2 years ago

basementmedia2 commented 2 years ago

Hi,

In Outlook i have created a button which onclick calls an url and provides the Email of the currently selected Email as parameter

the Visual Basic script looks like this

Option Explicit
Public Sub GetCurrentEmailInfo()
    Dim currentExplorer As Explorer
    Dim Selection As Selection
    Dim currentItem As Object
    Dim currentMail As MailItem
    Dim email_id As String

    Set currentExplorer = Application.ActiveExplorer
    Set Selection = currentExplorer.Selection

    email_id = Selection.Item(1).EntryID

    CreateObject("Wscript.Shell").Run "myurl/myphpfile.php?email_id=" & email_id
End Sub

The PHP i use to get the Email-Content

$email = $graph->createRequest("GET", "my@email.de/mailfolders/inbox/messages/AAMkAGE4NmI2MmZkLWJjNzktNDBmMS1hZGY4LWIxMzBjNmY4MWI3OQBGAAAAAAAlT71tgzMxT4lEEyvGxik9BwDrnNWima7tSIuOg9AgCoUfAAAAAAEMAADrnNWima7tSIuOg9AgCoUfAACloyrPAAA=")
->setReturnType(Model\Message::class)
->execute();

print_r($email);

This Request works and expects the Email-ID entryId format, wjhich looks like this

AAMkAGE4NmI2MmZkLWJjNzktNDBmMS1hZGY4LWIxMzBjNmY4MWI3OQBGAAAAAAAlT71tgzMxT4lEEyvGxik9BwDrnNWima7tSIuOg9AgCoUfAAAAAAEMAADrnNWima7tSIuOg9AgCoUfAACloyrPAAA=

But the EntryID coming from Outlook looks like this (Hex Format)

00000000254FBD6D8333314F8944132BC6C6293D0700EB9CD5A299AEED488B8E83D0200A851F00000000010C0000EB9CD5A299AEED488B8E83D0200A851F0000A5A32ACF0000

Question is: How can i convert this Hex-ID to the required entryId?

I've found this function in the API

https://docs.microsoft.com/de-de/graph/api/user-translateexchangeids?view=graph-rest-1.0&tabs=http

But this function does not accept the Hex-Id as sourceId-Type

Do you have an idea, how i get this thing working?

Best wishes

Daniel

zengin commented 2 years ago

Hi @basementmedia2,

Thanks for raising this issue. I think you are on the right track, but there is a little bit more processing that needs to be done. The entryId you get is in the hex string format, so

  1. You would first need to convert that into a byte array where each byte is represented by the hex characters in that string.
  2. Then you would need to get the base64 encoded string out of that.
  3. And lastly you would need to create a URL safe base64 string, where the encoding is documented here: exchangeIdFormat values
    Public Function GetCurrentEmailInfo()
        'assuming that you have the entryId at this line from the email
        Dim entryId As String
        entryId = "00000000254FBD6D8333314F8944132BC6C6293D0700EB9CD5A299AEED488B8E83D0200A851F00000000010C0000EB9CD5A299AEED488B8E83D0200A851F0000A5A32ACF0000"

        'convert entryId from hex string to byte array assuming every two characters are one byte
        Dim numberChars As Integer = entryId.Length
        Dim entryIdBytes(numberChars \ 2 - 1) As Byte
        For i As Integer = 0 To numberChars - 2 Step 2
            entryIdBytes(i \ 2) = Convert.ToByte(entryId.Substring(i, 2), 16)
        Next

        'convert entryIdBytes to Base64 string
        Dim entryIdBase64 As String = Convert.ToBase64String(entryIdBytes)

        'https://docs.microsoft.com/en-us/graph/api/user-translateexchangeids?view=graph-rest-1.0&tabs=http#exchangeidformat-values
        'URL-safeness is implemented by modifying the base64 encoding of the binary data in the following way:
        'Replace + with -
        'Replace / with _
        'Remove any trailing padding characters (=)
        'Add an integer to the end of the string indicating how many padding characters were in the original (0, 1, or 2)

        'count the number of padding characters in the original base64 string
        Dim paddingCount As Integer = entryIdBase64.Count(Function(c) c = "="c)
        Dim urlSafeBase64EntryId As String = entryIdBase64.Replace("=", "").Replace("+", "-").Replace("/", "_") & paddingCount.ToString()
        CreateObject("Wscript.Shell").Run "myurl/myphpfile.php?email_id=" & urlSafeBase64EntryId
    End Function

PHP code will need to call the translateExchangeIds endpoint to convert from entryId to restId and the resulting targetId can then be used as a messageId in Graph. I have tested this end to end, and it works.

Thanks for giving me the opportunity to play with VB for the first time, there might be a better and idiomatic way of implementing this in VB, but it does the job :)

basementmedia2 commented 2 years ago

Hi,

thank you very much for your help.

Unfortunately there are a few compiling errors:

grafik

when i e.g. split

Dim numberChars As Integer = entryId.Length

to

Dim numberChars As Integer numberChars = entryId.Length

i get this error ;-(

grafik

Sorry, my vb script knowledge is not really amazing

Best wishes Daniel

zengin commented 2 years ago

It looks like I had written the code in VB.NET and your code is in Outlook VBA. I am not sure how to convert one to another and what the full set of limitations are. Here is a link comparing the two: https://software-solutions-online.com/vba-vs-vb-net/#:~:text=As%20you%20can%20see%20from%20the%20explanations%20above%2C,standalone%20executable%2C%20you%20would%20need%20to%20use%20VB.Net.

If it is too much work to convert VB.NET code into Outlook VBA, alternatively, you can pass-in the entryID as a hex string directly to your PHP code, and make the conversion on the PHP side, which is relatively easier due to the built-in hex2bin and base64_encode functions.

// entryId is a hex string
$entryId = "00000000254FBD6D8333314F8944132BC6C6293D0700EB9CD5A299AEED488B8E83D0200A851F00000000010C0000EB9CD5A299AEED488B8E83D0200A851F0000A5A32ACF0000";

// convert entryId to a byte array
$entryIdBytes = hex2bin($entryId);

// convert entryIdBytes into a base64 string
$urlUnsafeEntryIdBase64 = base64_encode($entryIdBytes);

// URL-safeness is implemented by modifying the base64 encoding of the binary data in the following way:
// Replace + with -
// Replace / with _
// Remove any trailing padding characters (=)
// Add an integer to the end of the string indicating how many padding characters were in the original (0, 1, or 2)

// count the number of trailing padding characters (=)
$paddingCount = substr_count($urlUnsafeEntryIdBase64, "=");

// convert $urlUnsafeEntryIdBase64 to a URL-safe base64 string
$urlSafeEntryIdBase64 = str_replace("+", "-", $urlUnsafeEntryIdBase64);
$urlSafeEntryIdBase64 = str_replace("/", "_", $urlSafeEntryIdBase64);
$urlSafeEntryIdBase64 = rtrim($urlSafeEntryIdBase64, "=");
$urlSafeEntryIdBase64 = $urlSafeEntryIdBase64 . $paddingCount;

echo $urlSafeEntryIdBase64;

// call the translateExchangeIds to get the restId
// call other graph APIs such as messages with targetId returned
// ...
basementmedia2 commented 2 years ago

Hi,

thank you for being so patient with me ;-)

I tried this to convert the entryID to restID:

require_once __DIR__ . '/vendor/autoload.php';

// Include the Microsoft Graph classes
use Microsoft\Graph\Graph;
use Microsoft\Graph\Model;
$tenantId="mytenantID";
$clientId="myClientID";
$clientSecret="myClientSecret;

$guzzle = new \GuzzleHttp\Client();
$url = 'https://login.microsoftonline.com/' . $tenantId . '/oauth2/token?api-version=1.0';
$token = json_decode($guzzle->post($url, [
    'form_params' => [
        'client_id' => $clientId,
        'client_secret' => $clientSecret,
        'resource' => 'https://graph.microsoft.com/',
        'grant_type' => 'client_credentials',
    ],
])->getBody()->getContents());

$accessToken = $token->access_token;
$graph = new Graph();
$graph->setAccessToken($accessToken);

// Get the Entry_ID from Outlook
$email_id=rawurldecode($_GET["email_id"]);
// convert entryId to a byte array
$entryIdBytes = hex2bin($email_id);

// convert entryIdBytes into a base64 string
$urlUnsafeEntryIdBase64 = base64_encode($entryIdBytes);
// count the number of trailing padding characters (=)
$paddingCount = substr_count($urlUnsafeEntryIdBase64, "=");
// convert $urlUnsafeEntryIdBase64 to a URL-safe base64 string
$urlSafeEntryIdBase64 = str_replace("+", "-", $urlUnsafeEntryIdBase64);
$urlSafeEntryIdBase64 = str_replace("/", "_", $urlSafeEntryIdBase64);
$urlSafeEntryIdBase64 = rtrim($urlSafeEntryIdBase64, "=");
$urlSafeEntryIdBase64 = $urlSafeEntryIdBase64 . $paddingCount;

echo "Converted to Base64:<br>";
echo $urlSafeEntryIdBase64;

$email_id=$urlSafeEntryIdBase64;

// I think the following code up from here contains the error
$id_header = array(
    "inputIds" => [$email_id],
    "sourceIdType"        => "entryId",
    "targetIdType"        => "restId"
);

$email_id_conv = $graph->createRequest("POST", "/users/myemail@mydomain.de/translateExchangeIds")
->addHeaders($id_header)
->setReturnType(Model\Message::class)
->execute();

echo "Email-ID in restID-format:<br>";
print_t($email_id_conv);

I get the following error message:

Fatal error: Uncaught GuzzleHttp\Exception\ClientException: Client error: `POST https://graph.microsoft.com/v1.0/users/myemail@mydomain.de/translateExchangeIds` resulted in a `400 Bad Request` response: {"error":{"code":"ErrorInvalidParameter","message":"The value of the parameter 'InputIds' is empty."}} [...]
zengin commented 2 years ago

@basementmedia2, no worries, I think we are getting close to the solution :)

translateExchangeIds endpoint returns a collection of ConvertIdResult objects, not Message objects, so you'd need to set the return type accordingly. And inputIds are part of the body, not the headers. So you'd need something like:

$body= array(
    "inputIds" => [$email_id],
    "sourceIdType"        => "entryId",
    "targetIdType"        => "restId"
);

$convertIdResults = $graph->createRequest("POST", "/users/myemail@mydomain.de/translateExchangeIds")
->attachBody($body)
->setReturnType(Model\ConvertIdResult::class)
->execute();

echo "Email-ID in restID-format:<br>";
print_t($convertIdResults[0]->getTargetId());

I haven't tested the code above, so I may have a syntax error, but hopefully it gives an idea on how to proceed.

basementmedia2 commented 2 years ago

Works perfece! Thank you. Now i need your adress so that i can send you a pallet of beer ;-)

basementmedia2 commented 2 years ago

print_t must be print_r, but that was my fault

basementmedia2 commented 2 years ago

Thank you @zengin

zengin commented 2 years ago

@basementmedia2 glad to hear that the issue is resolved. Thanks for using Microsoft Graph PHP SDK.