EWS Script to automatically generate registry keys to enable Custom Forms in Outlook

As you know Custom form script is now disabled by default and you will need to create a set of registry keys as outlined in this KB. We have had a few customers who have a lot of custom forms published in the Organizational Forms Library and it can be painful to get the message class of each of those form and then create the registry entries. The script below iterates through all the forms published in the Organizational Forms Library and then generates the required set of registry keys.

This is a PowerShell script that uses EWS Managed API Ver 2.2 to access the Organizational Forms Library and generate the registry keys. This script will need to be run with credentials of a user that has access to the Organizational Forms Library.

 #
 # DumpMessageClassforForms.ps1
 #
 # By Akash Bhargava, Microsoft Ltd. 2017. Use at your own risk.  No warranties are given.
 # Thank you David for the Template
 # DISCLAIMER:
 # THIS CODE IS SAMPLE CODE. THESE SAMPLES ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
 # MICROSOFT FURTHER DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR OF FITNESS FOR
 # A PARTICULAR PURPOSE. THE ENTIRE RISK ARISING OUT OF THE USE OR PERFORMANCE OF THE SAMPLES REMAINS WITH YOU. IN NO EVENT SHALL
 # MICROSOFT OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS,
 # BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THE
 # SAMPLES, EVEN IF MICROSOFT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. BECAUSE SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION
 # OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU.
  
 param (
     [Parameter(Mandatory=$False,HelpMessage="Username used to authenticate with EWS")]
     [string]$AuthUsername,
     
     [Parameter(Mandatory=$False,HelpMessage="Password used to authenticate with EWS")]
     [string]$AuthPassword,
     
     [Parameter(Mandatory=$False,HelpMessage="Domain used to authenticate with EWS")]
     [string]$AuthDomain,
  
     [Parameter(Mandatory=$False,HelpMessage="EWS Url (if omitted, then autodiscover is used)")]    
     [string]$EwsUrl,
     
     [Parameter(Mandatory=$False,HelpMessage="Path to managed API (if omitted, a search of standard paths is performed)")]    
     [string]$EWSManagedApiPath = "",
     
     [Parameter(Mandatory=$False,HelpMessage="Whether to ignore any SSL errors (e.g. invalid certificate)")]    
     [switch]$IgnoreSSLCertificate,
     
     [Parameter(Mandatory=$False,HelpMessage="Bitness of Outlook and Windows")]
     [string]$Bitness = "x32on64",
     
     [Parameter(Mandatory=$False,HelpMessage="Log file - activity is logged to this file if specified")]    
     [string]$LogFile = ""
     
 )
  
 [string]$info = "White"                # Color for informational messages
 [string]$warning = "Yellow"            # Color for warning messages
 [string]$error = "Red"                 #Color for error messages
 [string]$success = "Green"             # Color for success messages
  
 [string]$LogFile = "Log.txt"   # Path of the Log File
 [string]$RegFile = "CustomFormKeys.reg"   # Path of the Reg File
  
  
 # Define our functions
 Function Log([string]$Details, [ConsoleColor]$Colour)
 {
     if ($Colour -eq $null)
     {
         $Colour = [ConsoleColor]::White
     }
     
     if ($verbose)
     {    Write-Host $Details -ForegroundColor $Colour }
         
     if ( $LogFile -eq "" ) { return    }
     (Get-Date).ToString()+" "+ $Details | Out-File $LogFile -Append
 }
  
 Function LoadEWSManagedAPI()
 {
     # Find and load the managed API
     
     if ( ![string]::IsNullOrEmpty($EWSManagedApiPath) )
     {
         if ( Test-Path $EWSManagedApiPath )
         {
             Add-Type -Path $EWSManagedApiPath
             return $true
         }
         Write-Host ( [string]::Format("Managed API not found at specified location: {0}", $EWSManagedApiPath) ) -ForegroundColor $error
     }
     
     $a = Get-ChildItem -Recurse "C:\Program Files (x86)\Microsoft\Exchange\Web Services" -ErrorAction SilentlyContinue | Where-Object { ($_.PSIsContainer -eq $false) -and ( $_.Name -eq "Microsoft.Exchange.WebServices.dll" ) }
     if (!$a)
     {
         $a = Get-ChildItem -Recurse "C:\Program Files\Microsoft\Exchange\Web Services" -ErrorAction SilentlyContinue | Where-Object { ($_.PSIsContainer -eq $false) -and ( $_.Name -eq "Microsoft.Exchange.WebServices.dll" ) }
     }
     
     if ($a)    
     {
         # Load EWS Managed API
         Write-Host ([string]::Format("Using managed API {0} found at: {1}", $a.VersionInfo.FileVersion, $a.VersionInfo.FileName)) -ForegroundColor $success
         Add-Type -Path $a.VersionInfo.FileName
         return $true
     }
     return $false
 }
  
 Function SearchOrganizationalFormsLibrary()
 {
  
     # Set EWS URL if specified, or use autodiscover if no URL specified.
     if ($EwsUrl)
     {
         $service.URL = New-Object Uri($EwsUrl)
     }
     
     $LogExists = Test-Path $LogFile
     if ($LogExists){Remove-Item  $LogFile}
     
     $RegExists = Test-Path $RegFile
     if ($RegExists){Remove-Item  $RegFile}
     
     
     $nl = [Environment]::NewLine
     $RegFixFile = $null;
     
     if ($Bitness)
     {
         if ($Bitness -eq "x32on64")
         {
             $RegFixFile="Windows Registry Editor Version 5.00" + $nl + "[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Office\16.0\Outlook\Security]" + $nl + "`"DisableCustomFormItemScript`"=dword:00000000" + $nl +
             "[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Office\16.0\Outlook\Forms\TrustedFormScriptList]"
     
         }
         else
         {
             $RegFixFile="Windows Registry Editor Version 5.00" + $nl + "[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\16.0\Outlook\Security]" + $nl + "`"DisableCustomFormItemScript`"=dword:00000000" + $nl +
             "[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\16.0\Outlook\Forms\TrustedFormScriptList]"
         }
     }
             
         Log "Looking for Custom Forms.." $info 
     
     #Bind to the Public Folders Root
     $PFRoot = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::PublicFoldersRoot)
     
     #$PFRoot
     #Get to Its Parent
     $ParentofPFRoot = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$PFRoot.ParentFolderId)
     
     #Folder View
     $oFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(10)
     
     #Item View
     $PR_OAB_NAME = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6800,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
     $oItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(500)
     $oItemView.PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet(
         [Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly,
         [Microsoft.Exchange.WebServices.Data.ItemSchema]::Subject,
         $PR_OAB_NAME)
     $oItemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Associated
     
     $oFindFolderResults = $service.FindFolders($ParentofPFRoot.Id,$oFolderView)
     
     #Now Bind to the NON_IPM_SUBTREE
     $NON_IPM_SUBTREE = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$oFindFolderResults.Folders[1].Id)
     
     ForEach ($subFolder in $NON_IPM_SUBTREE.FindFolders($oFolderView))
     {
         
         if ($subFolder.DisplayName -eq "EFORMS REGISTRY")
         {
             #Get to the "Organizational Forms Library"
             $oFindFolderResultsOFL = $service.FindFolders($subFolder.Id,$oFolderView)
             
             #Look at the associated contents folder..
             $results = $service.FindItems( $oFindFolderResultsOFL.Folders[0].Id, $oItemView )
             
             $RegFixFile | Out-File $RegFile -Append
             
             Log "Looping through the Messages..."
             
             ForEach ($item in $results.Items)
             {
                 $FormMessageClass =$null;
                 if ($Item.TryGetProperty($PR_OAB_NAME,[ref] $FormMessageClass))
                 {
  
                     #Generate the RegKeys..                
                     Log([string]::Format("Creating Key for :{0}", $FormMessageClass))
                     Write-Host ([string]::Format("Creating Key for :{0}", $FormMessageClass)) -ForegroundColor $success
             
                     '"' + $FormMessageClass +'"'+ "=" +'"' + '"' |Out-File $RegFile -Append
                 }
             }
         }
     }
     
             
     Log "Done!"
 }
  
 Function TrustAllCerts() {
     <#
     .SYNOPSIS
     Set certificate trust policy to trust self-signed certificates (for test servers).
     #>
  
     ## Code From https://poshcode.org/624
     ## Create a compilation environment
     $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider
     $Compiler=$Provider.CreateCompiler()
     $Params=New-Object System.CodeDom.Compiler.CompilerParameters
     $Params.GenerateExecutable=$False
     $Params.GenerateInMemory=$True
     $Params.IncludeDebugInformation=$False
     $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null
  
     $TASource=@'
         namespace Local.ToolkitExtensions.Net.CertificatePolicy {
         public class TrustAll : System.Net.ICertificatePolicy {
             public TrustAll()
             { 
             }
             public bool CheckValidationResult(System.Net.ServicePoint sp,
                                                 System.Security.Cryptography.X509Certificates.X509Certificate cert, 
                                                 System.Net.WebRequest req, int problem)
             {
                 return true;
             }
         }
         }
 '@ 
     $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
     $TAAssembly=$TAResults.CompiledAssembly
  
     ## We now create an instance of the TrustAll and attach it to the ServicePointManager
     $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")
     [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll
 }
  
 # The following is the main script
  
  
 # Check if we need to ignore any certificate errors
 # This needs to be done *before* the managed API is loaded, otherwise it doesn't work consistently (i.e. usually doesn't!)
 if ($IgnoreSSLCertificate)
 {
     Write-Host "WARNING: Ignoring any SSL certificate errors" -foregroundColor Yellow
     TrustAllCerts
 }
  
 # Load EWS Managed API
 if (!(LoadEWSManagedAPI))
 {
     Write-Host "Failed to locate EWS Managed API, cannot continue" -ForegroundColor $error
     Exit
 }
  
 #Create the service Object.
 $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
  
 # Set credentials if specified, or use logged on user.
  if ($AuthUsername -and $AuthPassword)
  {
     Write-Host "Applying given Credentials for:", $AuthUsername -ForegroundColor $info
     if ($AuthDomain)
     {
         $service.Credentials = New-Object  Microsoft.Exchange.WebServices.Data.WebCredentials($AuthUsername,$AuthPassword,$AuthDomain)
     } else {
         $service.Credentials = New-Object  Microsoft.Exchange.WebServices.Data.WebCredentials($AuthUsername,$AuthPassword)
     }
  
 } else {
     Write-Host "Using default credentials" -ForegroundColor Gray
     $service.UseDefaultCredentials = $true
  }
  
  
 Write-Host ""
 SearchOrganizationalFormsLibrary

By default the script will generate a .reg file for 32 bit Outlook on 64 bit Windows. If you want it for 64 bit Outlook on 64 bit Windows or 32 bit Outlook on 32 bit Windows, just specify -Bitness “x64on64” or -Bitness “x32on32”  in the command like below (technically an value other than “x32on64” will work). 

Sample command line below:

.\DumpMessageClassforForms.ps1 -AuthUsername admin –AuthPassword Pa$$w0rd –AuthDomain contoso -EwsUrl https://ex2013.contoso.com/ews/exchange.asmx
-IgnoreSSLCertificate

The registry file will be generated in the same folder where the script is run from.

Enjoy!