Hijacking the Cloud Legacy DN Writeback – Part 2

In Part 1 of this series, we learned of the possible issues with matched objects in a multi-forest topology. In Part 2, we will expand on the design and explain how this can be deployed even in production.

Before we proceed, it must be stated that creating custom rules in AD Connect can have a profound impact on your environment. We recommend rigorous testing in a lab environment and further testing in pre-production. The procedure suggested here is provided as-is and we will not detail every step for deploying custom rules.  As recommended by Microsoft please read Recommended Documents to gain expertise in synchronization rules.

Now that the legal stuff is out of the way, let’s remind ourselves why we are here?

When Azure AD Connect merges objects between forests such as a mail contact or a mailbox, only one value is sent to Azure AD. The impact? For users who have been utilizing the object in the other forest, they will encounter issues when their mailbox is migrated to Office 365.

In Part 1, we detailed the required steps to resolve this issue. First, create a new multi-value indexable attribute named onPremiseCustomAddresses in the metaverse. Second, populate this new attribute with x500 addresses from each forest by transforming the LegacyExchangeDN and striping the ProxyAddresses. Finally, we push these values back to all forests. 

To implement the solution, we will need to create:

  1. A new attribute in the AD Connect Metaverse
  2. 3 new inbound rules per forest (Arrows in Yellow)
  3. 2 new Outbound rules per forest (Arrows in Green)
Hijacking the Cloud Legacy DN Writeback – Part 2

Step 1: Create a Metaverse Attribute

Open AD Connect Synchronization Service Manager. Select Metaverse Designer, personAdd Attribute then New Attribute.

Attribute name: onPremiseCustomAddresses

Attribute type: String (indexable)

Multi-valued: Enabled

Hijacking the Cloud Legacy DN Writeback – Part 2

Step 2: Inbound Custom Rules

  1. Let’s first disable the sync engine by running the below CMDlet on the AD Connect server
  2. ADSyncScheduler -SyncCycleEnabled $false
  3. Let’s add the first of three rules for the forest corp.contoso.com and remember you will need to repeat these rules for each forest
  4. In from AD – Contact Custom Addresses
  5. From the Sync Rules Editor, select Inbound under Direction
  6. From the Sync Rules Editor, select the forest corp.contoso.com under Connector
  7. Click Add New Rule
  8. Enter a Name, we chose: ‘In from AD – Contact Custom Addresses’
  9. Click Description
  10. Under connected system, select the forest you would like to target ‘corp.contoso.com
  11. Connected System object type Contact
  12. Choose person as the Metaverse object type
  13. Select Join as the link type
  14. Enter a precedence value
  15. Click Next
  16. In the scoping filter area, select
  17. Add Clause. Under attribute, choose Mail and under Operator, select ISNOTNULL and true in the value Textbox
  18. Click Next
  19. Click Next on the Join rules page without entering anything
  20. Add a transformation:
  21. type: expression
  22. target attribute: onPremisesCustomAddresses
  23. source:

IIF(InStr([proxyAddresses],”smtp:”,1,vbTextCompare)>0 ||

    InStr([proxyAddresses],”SMTP:”,1,vbTextCompare)>0 ||

    InStr([proxyAddresses],”sip:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”SIP:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”x400:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”X400:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”ms:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”MS:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”ccmail:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”CCMAIL:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”eum:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”EUM:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”xmpp:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”XMPP:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”gwise:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”GWISE:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”fax:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”FAX:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”facsys:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”FACSYS:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”x500:/o=ExchangeLabs”,1,vbTextCompare)>0 ,NULL,[proxyAddresses])

  1. merge type: MergeCaseInsensitive
  2. Click Add
  3. Now for the second of three rules for the forest corp.contoso.com
  4. In from AD – User Custom Addresses
  5. From the Sync Rules Editor, select Inbound under Direction
  6. From the Sync Rules Editor, select the forest corp.contoso.com under Connector
  7. Click Add New Rule
  8. Enter a Name, we chose: ‘In from AD – Contact Custom Addresses’
  9. Description
  10. Under connected system, select the forest you would like to target ‘corp.contoso.com
  11. Connected System object type User
  12. Choose person as the Metaverse object type
  13. Select Join as the link type
  14. Enter a precedence value
  15. Click Next
  16. In the scoping filter area, select
  17. Add Clause. Under attribute, choose Mail and under Operator, select ISNOTNULL and true in the value Textbox
  18. Click Next
  19. Click Next on the Join rules page without entering anything
  20. Add a transformation:
  21. type: expression
  22. target attribute: onPremisesCustomAddresses
  23. source:

IIF(InStr([proxyAddresses],”smtp:”,1,vbTextCompare)>0 ||

    InStr([proxyAddresses],”SMTP:”,1,vbTextCompare)>0 ||

    InStr([proxyAddresses],”sip:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”SIP:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”x400:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”X400:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”ms:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”MS:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”ccmail:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”CCMAIL:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”eum:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”EUM:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”xmpp:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”XMPP:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”gwise:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”GWISE:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”fax:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”FAX:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”facsys:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”FACSYS:”,1,vbTextCompare)>0  ||

    InStr([proxyAddresses],”x500:/o=ExchangeLabs”,1,vbTextCompare)>0 ,NULL,[proxyAddresses])

  1. merge type: MergeCaseInsensitive
  2. Click Add
  3. Finally, for the last inbound rule, we will add the LegacyExchangeDN for contact objects as a x500 address in our custom value in the metaverse
  4. In from AD – LegacyExchangeDN
  5. From the Sync Rules Editor, select Inbound under Direction
  6. From the Sync Rules Editor, select the forest corp.contoso.com under Connector
  7. Click Add New Rule
  8. Enter a Name, we chose: ‘In from AD – Contact Custom Addresses’
  9. Go to Description
  10. Under the connected system, select the forest you would like to target ‘corp.contoso.com‘.
  11. Connected System object type Contact
  12. Choose person as the Metaverse object type
  13. Select Join as the link type
  14. Enter a precedence value
  15. Click Next
  16. In the scoping filter area, select
  17. Add Clause. Under attribute, choose LegacyExchangeDN and under Operator, select ISNOTNULL and true in the value Textbox
  18. Click Next
  19. Click Next on the Join rules page without entering anything
  20. Add a transformation:
  21. type: expression
  22. target attribute: onPremisesCustomAddresses
  23. source:
  24.                 IIF(IsNullOrEmpty([legacyExchangeDN]),NULL,”x500:” & [legacyExchangeDN])
  25. merge type: MergeCaseInsensitive
  26. Click Add
  27. Repeat these rules for each forest which requires matching

Step 3: Outbound Custom Rules

Now that the inbound rules have been created you may want to run a full sync and review the results in the metaverse search. Once you are happy with the inbound flows, it’s time to export your results to your forests.

  1. Create your first of two rules for the forest corp.contoso.com and remember once more you will need to repeat these rules for each forest
  2. Out to AD – Contact Custom Addresses
  3. From the Sync Rules Editor, select Outbound under Direction
  4. From the Sync Rules Editor, select the forest corp.contoso.com under Connector
  5. Click Add New Rule
  6. Enter a Name, we chose: ‘In from AD – Contact Custom Addresses’
  7. Click Description
  8. Under connected system, select the forest you would like to target ‘corp.contoso.com
  9. Connected System object type Contact
  10. Choose person as the Metaverse object type
  11. Select Join as the link type
  12. Enter a precedence value
  13. Click Next
  14. In the scoping filter area, select Add Clause
  15. Under attribute, choose onPremisesCustomAddresses and under Operator, select ISNOTNULL and true in the value Textbox
  16. Click Next
  17. Click Next on the Join rules page without entering anything
  18. Add a transformation:
  19. type: expression
  20. target attribute: proxyAddresses
  21. source:
  22. ‘IIF(IsNullOrEmpty([onPremisesCustomAddresses]),NULL,[onPremisesCustomAddresses])’
  23. merge type: MergeCaseInsensitive
  24. Click Add
  25. Out to AD – User Custom Addresses
  26. From the Sync Rules Editor, select Outbound under Direction.
  27. From the Sync Rules Editor, select the forest corp.contoso.com under Connector
  28. Click Add New Rule
  29. Enter a Name, we chose: ‘In from AD – User Custom Addresses’
  30. Click Description
  31. Under connected system, select the forest you would like to target ‘corp.contoso.com
  32. Connected System object type User   
  33. Choose person as the Metaverse object type
  34. Select Join as the link type
  35. Enter a precedence value
  36. Click Next
  37. In the scoping filter area, select Add Clause. Under attribute, choose onPremisesCustomAddresses and under Operator, select ISNOTNULL and true in the value Textbox
  38. Click Next
  39. Click Next on the Join rules page without entering anything
  40. Add a transformation:
  41. type: expression
  42. target attribute: proxyAddresses
  43. source:
  44. ‘IIF(IsNullOrEmpty([onPremisesCustomAddresses]),NULL,[onPremisesCustomAddresses])’
  45. merge type: MergeCaseInsensitive
  46. Click Add
  47. Run full sync and review the results. If the results are as expected it’s time to enable the sync engine once more by running the below CMDlet on the AD Connect server

ADSyncScheduler -SyncCycleEnabled $True

So that’s about it from me until next time, take care and stay safe implementing your custom AD Connect Rules.

PS. For the adventurous types please see the following PowerShell scripts. You might be interested in the random precedence order that was chosen.

In from AD – Contact Custom Addresses

 Change corp.contoso.com
 [string]$Connector = (Get-ADSyncConnector | ? { ($_.ConnectorTypeName -eq "AD")-and ($_.Name -eq 'corp.contoso.com')}).Identifier.ToString()
 New-ADSyncRule  
 -Name 'In from AD - Contact Custom Addresses'
 -Identifier $Identifier 
-
Description 'This Rule has been created to collect all Custom Addresses in each forest. This will select all values that are not smtp, SIP or x500 addresses from EOP. Please be aware that the any other custom addresses such as CcMAIL: or Ms: will also be imported by default. If you do not want these addresses please remove these addresses from the proxy addresses before applying the rule.'
-Direction 'Inbound'  -Precedence 90
-PrecedenceAfter '00000000-0000-0000-0000-000000000000'  
-PrecedenceBefore '00000000-0000-0000-0000-000000000000'
-SourceObjectType 'contact'  -TargetObjectType 'person'
-Connector $Connector  -LinkType 'Join'
-SoftDeleteExpiryInterval 0  -ImmutableTag ''
-OutVariable syncRule

Add-ADSyncAttributeFlowMapping   
-SynchronizationRule $syncRule[0]
-Source @('proxyAddresses')
-Destination 'onPremisesCustomAddresses'
-FlowType 'Expression'  
-ValueMergeType 'MergeCaseInsensitive'
-Expression '

IIF(InStr([proxyAddresses],"smtp:",1,vbTextCompare)>0 ||  InStr([proxyAddresses],"SMTP:",1,vbTextCompare)>0 ||  InStr([proxyAddresses],"sip:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"SIP:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"x400:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"X400:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"ms:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"MS:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"ccmail:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"CCMAIL:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"eum:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"EUM:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"xmpp:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"XMPP:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"gwise:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"GWISE:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"fax:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"FAX:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"facsys:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"FACSYS:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"x500:/o=ExchangeLabs",1,vbTextCompare)>0 ,NULL,[proxyAddresses]) 
 ' `
-OutVariable syncRule

New-Object   ` 
-TypeName 'Microsoft.IdentityManagement.PowerShell.ObjectModel.ScopeCondition'
-ArgumentList 'mail','True','ISNOTNULL' `
-OutVariable condition0
 
Add-ADSyncScopeConditionGroup   
-SynchronizationRule $syncRule[0]
-ScopeConditions @($condition0[0]) `
-OutVariable syncRule

Add-ADSyncRule  `
 -SynchronizationRule $syncRule[0]
 
Get-ADSyncRule  `
 -Identifier $Identifier

In from AD – LegacyExchangeDN

[string]$Identifier = [Guid]::NewGuid().ToString()
Change corp.contoso.com
[string]$Connector = (Get-ADSyncConnector | ? { ($_.ConnectorTypeName -eq "AD")-and ($_.Name -eq 'corp.contoso.com')}).Identifier.ToString()

New-ADSyncRule   
-Name 'In from AD - LegacyExchangeDN'
-Identifier $Identifier  -Description ''
-Direction 'Inbound'  -Precedence 99
-PrecedenceAfter '00000000-0000-0000-0000-000000000000' 
-PrecedenceBefore '00000000-0000-0000-0000-000000000000'
-SourceObjectType 'contact'  -TargetObjectType 'person'
-Connector $Connector  -LinkType 'Join'
-SoftDeleteExpiryInterval 0  -ImmutableTag ''
-OutVariable syncRule

Add-ADSyncAttributeFlowMapping 
-SynchronizationRule $syncRule[0]
-Source @('legacyExchangeDN')
-Destination 'onPremisesCustomAddresses'
-FlowType 'Expression'
-ValueMergeType 'MergeCaseInsensitive'
-Expression 'IIF(IsNullOrEmpty([legacyExchangeDN]),NULL,"x500:" & [legacyExchangeDN])' `
-OutVariable syncRule

New-Object   
-TypeName 'Microsoft.IdentityManagement.PowerShell.ObjectModel.ScopeCondition'
-ArgumentList legacyExchangeDN,'True','ISNOTNULL' `
-OutVariable condition0

Add-ADSyncScopeConditionGroup   
-SynchronizationRule $syncRule[0]
-ScopeConditions @($condition0[0]) `
-OutVariable syncRule

Add-ADSyncRule  `
 -SynchronizationRule $syncRule[0]

Get-ADSyncRule  `
 -Identifier $Identifier

In from AD – User Custom Addresses

[string]$Identifier = [Guid]::NewGuid().ToString()
Change corp.contoso.com
[string]$Connector = (Get-ADSyncConnector | ? { ($_.ConnectorTypeName -eq "AD")-and ($_.Name -eq 'corp.contoso.com')}).Identifier.ToString()
 
New-ADSyncRule
-Name 'In from AD - User Custom Addresses'
-Identifier $Identifier
-Description 'This Rule has been created to collect all Custom Adddresses in each forest. This will select all values that are not smtp, SIP or x500 addresses from EOP. Please be aware that the any other custom addresses such as CCMAIL: or MS: will also be imported by default. If you do not want these addresses please remove these addresses from the proxy addresses before applying the rule.
-Direction 'Inbound'  -Precedence 89
-PrecedenceAfter '00000000-0000-0000-0000-000000000000'  
-PrecedenceBefore '00000000-0000-0000-0000-000000000000'
-SourceObjectType 'user'  -TargetObjectType 'person'
-Connector $Connector  -LinkType 'Join'
-SoftDeleteExpiryInterval 0  -ImmutableTag ''
-OutVariable syncRule
 
Add-ADSyncAttributeFlowMapping  
-SynchronizationRule $syncRule[0]
-Source @('proxyAddresses')
-Destination 'onPremisesCustomAddresses'
-FlowType 'Expression'  
-ValueMergeType 'MergeCaseInsensitive'
-Expression '
 
IIF(InStr([proxyAddresses],"smtp:",1,vbTextCompare)>0 ||  InStr([proxyAddresses],"SMTP:",1,vbTextCompare)>0 ||  InStr([proxyAddresses],"sip:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"SIP:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"x400:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"X400:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"ms:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"MS:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"ccmail:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"CCMAIL:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"eum:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"EUM:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"xmpp:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"XMPP:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"gwise:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"GWISE:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"fax:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"FAX:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"facsys:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"FACSYS:",1,vbTextCompare)>0  ||  InStr([proxyAddresses],"x500:/o=ExchangeLabs",1,vbTextCompare)>0 ,NULL,[proxyAddresses]) 
 ' `
 -OutVariable syncRule
 New-Object   
-TypeName 'Microsoft.IdentityManagement.PowerShell.ObjectModel.ScopeCondition'
-ArgumentList 'mail','True','ISNOTNULL' `
-OutVariable condition0
 Add-ADSyncScopeConditionGroup   
-SynchronizationRule $syncRule[0]
-ScopeConditions @($condition0[0]) `
-OutVariable syncRule
 
Add-ADSyncRule  `
-SynchronizationRule $syncRule[0]
 
Get-ADSyncRule  `
-Identifier $Identifier

Out to AD – Contact Custom Addresses

[string]$Connector = (Get-ADSyncConnector | ? { ($_.ConnectorTypeName -eq "AD")-and ($_.Name -eq 'corp.contoso.com')}).Identifier.ToString()
 New-ADSyncRule   
-Name 'Out to AD - Contact Custom Addresses'
-Identifier $Identifier  
-Description '  This Rule has been created to write back all Custom Addresses found in each forest.'
-Direction 'Outbound'  -Precedence 62
-PrecedenceAfter '00000000-0000-0000-0000-000000000000'  
-PrecedenceBefore '00000000-0000-0000-0000-000000000000'
-SourceObjectType 'person'  
-TargetObjectType 'contact'
-Connector $Connector  
-LinkType 'Join'
-SoftDeleteExpiryInterval 0  
-ImmutableTag ''
-OutVariable syncRule
 
Add-ADSyncAttributeFlowMapping   
-SynchronizationRule $syncRule[0]
-Source @('onPremisesCustomAddresses')  
-Destination 'proxyAddresses'
-FlowType 'Expression' 
-ValueMergeType 'MergeCaseInsensitive'
-Expression 'IIF(IsNullOrEmpty([onPremisesCustomAddresses]),NULL,[onPremisesCustomAddresses])' `
 -OutVariable syncRule

New-Object   
-TypeName 'Microsoft.IdentityManagement.PowerShell.ObjectModel.ScopeCondition'
 -ArgumentList 'onPremisesCustomAddresses','True','ISNOTNULL' `
 -OutVariable condition0
 
Add-ADSyncScopeConditionGroup   
-SynchronizationRule $syncRule[0]
-ScopeConditions @($condition0[0]) `
-OutVariable syncRule
 
Add-ADSyncRule  `
-SynchronizationRule $syncRule[0]

Get-ADSyncRule  `
 -Identifier $Identifier

Out to AD – User Custom Addresses

[string]$Connector = (Get-ADSyncConnector | ? { ($_.ConnectorTypeName -eq "AD")-and ($_.Name -eq 'corp.contoso.com')}).Identifier.ToString()  

New-ADSyncRule  
-Name 'Out to AD - User Custom Addresses'  
-Identifier $Identifier  
-Description '  This Rule has been created to write back all Custom Addresses found in each forest.'  
-Direction 'Outbound'  
-Precedence 57  
-PrecedenceAfter '00000000-0000-0000-0000-000000000000'  
-PrecedenceBefore '00000000-0000-0000-0000-000000000000'  
-SourceObjectType 'person'  
-TargetObjectType 'user'  
-Connector $Connector  -LinkType 'Join'  
-SoftDeleteExpiryInterval 0  
-ImmutableTag ''  
-OutVariable syncRule  Add-ADSyncAttributeFlowMapping  
-SynchronizationRule $syncRule[0]  
-Source @('onPremisesCustomAddresses') 
-Destination 'proxyAddresses'  
-FlowType 'Expression' 
-ValueMergeType 'MergeCaseInsensitive'  
-Expression 'IIF(IsNullOrEmpty([onPremisesCustomAddresses]),NULL,[onPremisesCustomAddresses])' `  
-OutVariable syncRule  New-Object   
-TypeName 'Microsoft.IdentityManagement.PowerShell.ObjectModel.ScopeCondition'  
-ArgumentList 'onPremisesCustomAddresses','True','ISNOTNULL' `  
-OutVariable condition0  

Add-ADSyncScopeConditionGroup   
-SynchronizationRule $syncRule[0]  
-ScopeConditions @($condition0[0]) `  
-OutVariable syncRule  Add-ADSyncRule  ` 
-SynchronizationRule $syncRule[0]  

Get-ADSyncRule  `  
-Identifier $Identifier

About the Author

Joshua Bines

Joshua is a Freelance Technical Consultant providing specialized professional services to support Office 365. Forever a tech enthusiast, his focus is developing critical skills to solve complex problems and helping others, ‘get stuff done!’ His pleasure is speaking at events, digging deep into technical topics and sharing learnt knowledge with fellow engineers. Connect with Josh on LinkedIn.

Leave a Reply