Forum Discussion

brogyi's avatar
brogyi
Brass Contributor
Jul 25, 2024

Exchange online and MGGraph are interfering

I am creating a new script. The script is running unattended. The script doing a couple things, the important part here is: setting a new user a license and setting the new user mailbox Address book policy. The address book is an Exchange online task the license is Graph. I am getting an error.

 

How to reproduce the error:

1. connect to MgGraph, I am using this command

Connect-MgGraph -TenantId $tenantID -AppId $appID -CertificateThumbprint $CertificateThumbPrint -NoWelcome

 

do some work, Disconnect-MgGraph

 

2. Connect to Exchange online, in the same script:

Connect-ExchangeOnline -CertificateThumbPrint $CertificateThumbPrint -AppID $appID -Organization $tenantID -CommandName Get-EXOMailbox,Get-mailbox,Set-mailbox -SkipLoadingCmdletHelp -ShowBanner:$false

 

The command verbose debug output is this:

 

 

 

DEBUG: 
using System; 
using System.Net;
using System.Management.Automation;
using Microsoft.Win32.SafeHandles;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.Versioning;
using System.Security;

namespace Microsoft.PowerShell.Commands.PowerShellGet 
{ 
    public static class Telemetry  
    { 
        public static void TraceMessageArtifactsNotFound(string[] artifactsNotFound, string operationName) 
        { 
            Microsoft.PowerShell.Telemetry.Internal.TelemetryAPI.TraceMessage(operationName, new { ArtifactsNotFound = artifactsNotFound });
        }         
        
        public static void TraceMessageNonPSGalleryRegistration(string sourceLocationType, string sourceLocationHash, string installationPolicy, strin
g packageManagementProvider, string publishLocationHash, string scriptSourceLocationHash, string scriptPublishLocationHash, string operationName) 
        { 
            Microsoft.PowerShell.Telemetry.Internal.TelemetryAPI.TraceMessage(operationName, new { SourceLocationType = sourceLocationType, SourceLoca
tionHash = sourceLocationHash, InstallationPolicy = installationPolicy, PackageManagementProvider = packageManagementProvider, PublishLocationHash = p
ublishLocationHash, ScriptSourceLocationHash = scriptSourceLocationHash, ScriptPublishLocationHash = scriptPublishLocationHash });
        }         
        
    }
    
    /// <summary>
    /// Used by Ping-Endpoint function to supply webproxy to HttpClient
    /// We cannot use System.Net.WebProxy because this is not available on CoreClr
    /// </summary>
    public class InternalWebProxy : IWebProxy
    {
        Uri _proxyUri;
        ICredentials _credentials;

        public InternalWebProxy(Uri uri, ICredentials credentials)
        {
            Credentials = credentials;
            _proxyUri = uri;
        }

        /// <summary>
        /// Credentials used by WebProxy
        /// </summary>
        public ICredentials Credentials
        {
            get
            {
                return _credentials;
            }
            set
            {
                _credentials = value;
            }
        }

        public Uri GetProxy(Uri destination)
        {
            return _proxyUri;
        }

        public bool IsBypassed(Uri host)
        {
            return false;
        }
    } 

    [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
    public struct CERT_CHAIN_POLICY_PARA {
        public CERT_CHAIN_POLICY_PARA(int size) {
            cbSize = (uint) size;
            dwFlags = 0;
            pvExtraPolicyPara = IntPtr.Zero;
        }
        public uint   cbSize;
        public uint   dwFlags;
        public IntPtr pvExtraPolicyPara; 
    }

    [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
    public struct CERT_CHAIN_POLICY_STATUS {
        public CERT_CHAIN_POLICY_STATUS(int size) {
            cbSize = (uint) size;
            dwError = 0;
            lChainIndex = IntPtr.Zero;
            lElementIndex = IntPtr.Zero;
            pvExtraPolicyStatus = IntPtr.Zero;
        }
        public uint   cbSize;
        public uint   dwError;
        public IntPtr lChainIndex;
        public IntPtr lElementIndex;
        public IntPtr pvExtraPolicyStatus; 
    }

    // Internal SafeHandleZeroOrMinusOneIsInvalid class to remove the dependency on .Net Framework 4.6.
    public abstract class InternalSafeHandleZeroOrMinusOneIsInvalid : SafeHandle
    {
        protected InternalSafeHandleZeroOrMinusOneIsInvalid(bool ownsHandle)
            : base(IntPtr.Zero, ownsHandle)
        {
        }

        public override bool IsInvalid
        {
            get
            {
                return handle == IntPtr.Zero || handle == new IntPtr(-1);
            }
        }
    }

    // Internal SafeX509ChainHandle class to remove the dependency on .Net Framework 4.6.
    [SecurityCritical]
    public sealed class InternalSafeX509ChainHandle : InternalSafeHandleZeroOrMinusOneIsInvalid { 
        private InternalSafeX509ChainHandle () : base(true) {}
 
        internal InternalSafeX509ChainHandle (IntPtr handle) : base (true) {
            SetHandle(handle); 
        }
  
        internal static InternalSafeX509ChainHandle InvalidHandle { 
            get { return new InternalSafeX509ChainHandle(IntPtr.Zero); }
        } 
 
        [SecurityCritical]
        override protected bool ReleaseHandle() 
        {
            CertFreeCertificateChain(handle);
            return true;
        } 

        [DllImport("Crypt32.dll", SetLastError=true)]

        [SuppressUnmanagedCodeSecurity,
         ResourceExposure(ResourceScope.None),
         ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        
        private static extern void CertFreeCertificateChain(IntPtr handle); 
    }

    public class Win32Helpers
    {
        [DllImport("Crypt32.dll", CharSet=CharSet.Auto, SetLastError=true)]
        public extern static 
        bool CertVerifyCertificateChainPolicy(
            [In]     IntPtr                       pszPolicyOID,
            [In]     SafeX509ChainHandle  pChainContext,
            [In]     ref CERT_CHAIN_POLICY_PARA   pPolicyPara,
            [In,Out] ref CERT_CHAIN_POLICY_STATUS pPolicyStatus);

        [DllImport("Crypt32.dll", CharSet=CharSet.Auto, SetLastError=true)]
        public static extern
        SafeX509ChainHandle CertDuplicateCertificateChain(
            [In]     IntPtr pChainContext);

        [DllImport("Crypt32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    
        [ResourceExposure(ResourceScope.None)]
    
        public static extern
        SafeX509ChainHandle CertDuplicateCertificateChain(
            [In]     SafeX509ChainHandle pChainContext);

        public static bool IsMicrosoftCertificate([In] SafeX509ChainHandle pChainContext)
        {
            //-------------------------------------------------------------------------
            //  CERT_CHAIN_POLICY_MICROSOFT_ROOT  
            //  
            //  Checks if the last element of the first simple chain contains a  
            //  Microsoft root public key. If it doesn't contain a Microsoft root  
            //  public key, dwError is set to CERT_E_UNTRUSTEDROOT.  
            //  
            //  pPolicyPara is optional. However,  
            //  MICROSOFT_ROOT_CERT_CHAIN_POLICY_ENABLE_TEST_ROOT_FLAG can be set in  
            //  the dwFlags in pPolicyPara to also check for the Microsoft Test Roots.  
            //  
            //  MICROSOFT_ROOT_CERT_CHAIN_POLICY_CHECK_APPLICATION_ROOT_FLAG can be set  
            //  in the dwFlags in pPolicyPara to check for the Microsoft root for  
            //  application signing instead of the Microsoft product root. This flag  
            //  explicitly checks for the application root only and cannot be combined  
            //  with the test root flag.    
            //  
            //  MICROSOFT_ROOT_CERT_CHAIN_POLICY_DISABLE_FLIGHT_ROOT_FLAG can be set  
            //  in the dwFlags in pPolicyPara to always disable the Flight root.  
            //  
            //  pvExtraPolicyPara and pvExtraPolicyStatus aren't used and must be set  
            //  to NULL.  
            //--------------------------------------------------------------------------  
            const uint MICROSOFT_ROOT_CERT_CHAIN_POLICY_ENABLE_TEST_ROOT_FLAG       = 0x00010000;
            const uint MICROSOFT_ROOT_CERT_CHAIN_POLICY_CHECK_APPLICATION_ROOT_FLAG = 0x00020000;
            //const uint MICROSOFT_ROOT_CERT_CHAIN_POLICY_DISABLE_FLIGHT_ROOT_FLAG    = 0x00040000;

            CERT_CHAIN_POLICY_PARA PolicyPara = new CERT_CHAIN_POLICY_PARA(Marshal.SizeOf(typeof(CERT_CHAIN_POLICY_PARA)));
            CERT_CHAIN_POLICY_STATUS PolicyStatus = new CERT_CHAIN_POLICY_STATUS(Marshal.SizeOf(typeof(CERT_CHAIN_POLICY_STATUS)));
            int CERT_CHAIN_POLICY_MICROSOFT_ROOT = 7;
            
            PolicyPara.dwFlags = (uint) MICROSOFT_ROOT_CERT_CHAIN_POLICY_ENABLE_TEST_ROOT_FLAG;
            bool isMicrosoftRoot = false;

            if(CertVerifyCertificateChainPolicy(new IntPtr(CERT_CHAIN_POLICY_MICROSOFT_ROOT),
                                                pChainContext,
                                                ref PolicyPara,
                                                ref PolicyStatus))
            {
                isMicrosoftRoot = (PolicyStatus.dwError == 0);
            }

            // Also check for the Microsoft root for application signing if the Microsoft product root verification is unsuccessful.
            if(!isMicrosoftRoot)
            {
                // Some Microsoft modules can be signed with Microsoft Application Root instead of Microsoft Product Root,
                // So we need to use the MICROSOFT_ROOT_CERT_CHAIN_POLICY_CHECK_APPLICATION_ROOT_FLAG for the certificate verification.
                // MICROSOFT_ROOT_CERT_CHAIN_POLICY_CHECK_APPLICATION_ROOT_FLAG can not be used
                // with MICROSOFT_ROOT_CERT_CHAIN_POLICY_ENABLE_TEST_ROOT_FLAG,
                // so additional CertVerifyCertificateChainPolicy call is required to verify the given certificate is in Microsoft Application Root.
                //
                CERT_CHAIN_POLICY_PARA PolicyPara2 = new CERT_CHAIN_POLICY_PARA(Marshal.SizeOf(typeof(CERT_CHAIN_POLICY_PARA)));
                CERT_CHAIN_POLICY_STATUS PolicyStatus2 = new CERT_CHAIN_POLICY_STATUS(Marshal.SizeOf(typeof(CERT_CHAIN_POLICY_STATUS)));
                PolicyPara2.dwFlags = (uint) MICROSOFT_ROOT_CERT_CHAIN_POLICY_CHECK_APPLICATION_ROOT_FLAG;

                if(CertVerifyCertificateChainPolicy(new IntPtr(CERT_CHAIN_POLICY_MICROSOFT_ROOT),
                                                    pChainContext,
                                                    ref PolicyPara2,
                                                    ref PolicyStatus2))
                {
                    isMicrosoftRoot = (PolicyStatus2.dwError == 0);
                }
            }

            return isMicrosoftRoot;
        }
    }
} 
IDX12729: Unable to decode the header '[PII of type 'System.String' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]' as Base64Url
 encoded string.
At C:\Program Files\WindowsPowerShell\Modules\ExchangeOnlineManagement\3.5.1\netFramework\ExchangeOnlineManagement.psm1:762 char:21
+                     throw $_.Exception.InnerException;
+                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (:) [], ArgumentException
    + FullyQualifiedErrorId : IDX12729: Unable to decode the header '[PII of type 'System.String' is hidden. For more details, see https://aka.ms/Id 
   entityModel/PII.]' as Base64Url encoded string.

 

 

Exception details:

$exception.Exception.Message
# empty $exception.Exception.ParamName
$exception.Exception.TargetSite
# empty $exception.Exception.Data 
$exception.Exception.InnerException
#empty $exception.Exception.HelpLink
$exception.Exception.Source
$exception.Exception.HResult
$exception.Exception.StackTrace
IDX12729: Unable to decode the header '[PII of type 'System.String' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]' as Base64Url 
encoded string.


Name                       : Decode
DeclaringType              : System.IdentityModel.Tokens.Jwt.JwtSecurityToken
ReflectedType              : System.IdentityModel.Tokens.Jwt.JwtSecurityToken
MemberType                 : Method
MetadataToken              : 100663422
Module                     : System.IdentityModel.Tokens.Jwt.dll
IsSecurityCritical         : True
IsSecuritySafeCritical     : False
IsSecurityTransparent      : False
MethodHandle               : System.RuntimeMethodHandle
Attributes                 : PrivateScope, Assembly, HideBySig
CallingConvention          : Standard, HasThis
ReturnType                 : System.Void
ReturnTypeCustomAttributes : Void 
ReturnParameter            : Void 
IsGenericMethod            : False
IsGenericMethodDefinition  : False
ContainsGenericParameters  : False
MethodImplementationFlags  : Managed
IsPublic                   : False
IsPrivate                  : False
IsFamily                   : False
IsAssembly                 : True
IsFamilyAndAssembly        : False
IsFamilyOrAssembly         : False
IsStatic                   : False
IsFinal                    : False
IsVirtual                  : False
IsHideBySig                : True
IsAbstract                 : False
IsSpecialName              : False
IsConstructor              : False
CustomAttributes           : {}

A metódus nem található: „Void System.Text.Json.Utf8JsonReader..ctor(System.ReadOnlySpan`1<Byte>, System.Text.Json.JsonReaderOptions)”.
System.IdentityModel.Tokens.Jwt
-2147024809
   a következő helyen: System.IdentityModel.Tokens.Jwt.JwtSecurityToken.Decode(String[] tokenParts, String rawData)
   a következő helyen: Microsoft.Exchange.Management.AdminApiProvider.Authentication.JwtSecurityTokenUtils.GetTenantId(String accessToken)
   a következő helyen: Microsoft.Exchange.Management.AdminApiProvider.Authentication.TokenProviderUtils.GetTokenInformation(AuthenticationResult token
AcquisitionResult, TokenProviderContext context)
   a következő helyen: Microsoft.Exchange.Management.AdminApiProvider.Authentication.MSALTokenProvider.<GetAccessTokenAsync>d__34.MoveNext()

 

I tired:

  • updateing both modules to the latest version
  • removeing the  Microsoft.Graph and Microsoft.Graph.Authentication module before connecting to Exchange online
  • clearing the token cache file from AppData\Local\.IdentityService\mg.msal.cache

 

I would like to avoid running two separate script or script isolation like new processes or jobs. Because i need to pass many variables between the two script, input and output.

 

The app I am using and the cert is okay. If i am running separately it is working, so I can connect Exchange online with it.

 

This github issue: https://github.com/microsoftgraph/msgraph-sdk-powershell/issues/1816 seems to have similar issue, but this was in 2023. There is a workaround, but I am unable to understand. Basically i should connect differently to Graph? or Exchange online? if so how? can anyone recommend a non interactive option?

 

Any idea why is this happening? What should i check?

  • Prasanth666 

     

    The issue is that ExchangeOnlineManagement 3.5.1 uses version 8.0.23.53103 of System.Text.Json.dll where 3.4.x doesn't use System.Text.Json.dll at all, hence there's no conflict with version 6.0.21.52210 used by the Microsoft.Graph.Authentication module.

     

    Cheers,

    Lain

  • Prasanth666's avatar
    Prasanth666
    Copper Contributor

    I have come across the same issue. It seems to be an issue with Exchange Online module 3.5.0 and later. Uninstalled Exchange Online module 3.5.0 and installed 3.4.0 to fix the issue.

    • LainRobertson's avatar
      LainRobertson
      Silver Contributor

      Prasanth666 

       

      The issue is that ExchangeOnlineManagement 3.5.1 uses version 8.0.23.53103 of System.Text.Json.dll where 3.4.x doesn't use System.Text.Json.dll at all, hence there's no conflict with version 6.0.21.52210 used by the Microsoft.Graph.Authentication module.

       

      Cheers,

      Lain

      • brogyi's avatar
        brogyi
        Brass Contributor
        Thank you all for your help. I uninstalled the ExchangeOnlineManagement module 3.5.x version and replaced with 3.4.0. Now it is working.
  • rrtt11's avatar
    rrtt11
    Copper Contributor
    I had the same issue and had to revert to 3.4.0. It worked.

    Is there any fix coming? Or do we just have to hope it works in future versions with the other modules?
    • LainRobertson's avatar
      LainRobertson
      Silver Contributor

      rrtt11 

       

      The risk of underlying libraries from one module being incompatible with another will always be there.

       

      All you can do is what you have done, which is test interoperability and use the versions that happily coexist.

       

      You will find that there will be periods where the latest versions of the modules will work together, but unless the teams writing the Exchange and Graph modules actively collaborate and coordinate working releases, it's inevitable that this scenario where the latest version of each do not work together will continue to crop up.

       

      Cheers,

      Lain

  • LainRobertson's avatar
    LainRobertson
    Silver Contributor

    brogyi 

     

    Hi, Peter.

     

    This isn't a particularly uncommon scenario and is caused by different PowerShell modules being aligned to different underlying .NET libraries.

     

    The key is to find which versions of which PowerShell modules can happily coexist, which is not as simple as saying "use the latest version of each module", since they're released on their individual cadences. It's not like the authors of one module go out and test interoperability with all the available versions of every other module available.

     

    In you case, the error near the bottom of your second dump indicates there's a conflict on the constructor of the [System.Text.Json.Utf8JsonReader] struct. I can't read the language the error's in but it doesn't really matter.

     

    If you load only the Exchange Online module and run the following command, what version information does it produce? And then if you close that PowerShell window, open another, then load the Graph module and re-run the same command, what version information does that yield?

     

    I'd expect the file locations and the version numbers will differ, and this will be where the error is coming from.

     

    Whichever module is loaded first will likely work since it's loaded the version of System.Text.Json.dll it's aligned to, while the second module won't be able to load its own version since a given module is only loaded once into the PowerShell process.

     

    Diagnostic command

    (Get-Item -Path "$([System.Text.Json.Utf8JsonReader].Assembly.Location)").VersionInfo;

     

    Ultimately, even knowing this version information doesn't intrinsically provide a solution. It's use is in determining which modules are likely to be compatible using the "load one module and check, then load the other module and check" approach.

     

    For example, if you test with different versions of the Exchange and Graph modules and find both settle on a common version number, then this specific error should go away (though there's nothing to say a different module conflict won't arise).

     

    Honestly, it'd probably be faster to simply try the last two or three versions of each until you find a working combination, but the above command will assist you if you want to go about it more forensically.

     

    Cheers,

    Lain

  • TValenta's avatar
    TValenta
    Copper Contributor
    I get the same error when I do Connect-AzAccount -Identity (module version 3.0.4) and Connext-ExchangeOnline or Connect-IPPSSession (EXO module version 3.5.1).
    IDX12729: Unable to decode the header '[PII of type 'System.String' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]' as Base64Url encoded string.

    Downgrading to EXO module 3.4.0 solved the problem, and I can use the Azure and EXO connections simultaneously. This is on Windows 10 with, and PowerShell 5.1.19041.4894

    I'd be happy to know if there is any change in the future regarding the conflicting .Nets.
      • TValenta's avatar
        TValenta
        Copper Contributor

        Harm_Veenstra 

        Thank you Harm, I noticed this version, and my quick testing shows improvement:
        * Connect-ExchangeOnline works with interactive login, managed identity, and a certificate (App Registration).
        * With all login methods above, I can also do AzConnect and MgConnect with a Managed Identity or a certificate (App Registration).
        * Sadly, connecting to IPPSSession is only possible with a Personal Account (interactively). Managed Identity was never supported to Purview (and probably never will be), and App Registration with certificate now produces a very unspecific error:
        ---
        An error has occurred.
        At C:\Program Files\WindowsPowerShell\Modules\ExchangeOnlineManagement\3.6.0\netFramework\ExchangeOnlineManagement.psm1:766 char:21
        + throw $_.Exception;
        + ~~~~~~~~~~~~~~~~~~
        + CategoryInfo : OperationStopped: (:) [], SystemException
        + FullyQualifiedErrorId : An error has occurred.
        ---
        Just trying to connect to IPPSSession will load some assemblies, which will cause Connect-MgGraph to throw the following error: Connect-MgGraph: Invalid JWT access token.
        This won't happen when connecting to IPPSSession with a Personal Account (interactively).

        Sadly, I need to connect to Purview with App Registration while simultaneously connecting to Azure and MgGraph. This is a pretty wild combination, but the client project requires it.


        After all, I will have to stick with 3.4.0, but thanks for the update.

Resources