Windows PowerShell scripts to register a .NET-based add-in for a COM-based host application

Before Visual Studio 2005 introduced XML-based registration for add-ins with an .AddIn file (which enabled X-Copy deployment), add-ins for Microsoft applications required two steps to be registered:

  • To register the add-in dll as ActiveX (COM) component
  • To register the add-in dll as add-in for the host application through some registry entries

This is still true for COM-based add-ins for Visual Studio (any version) and for other hosts such as Microsoft Office or its VBA editor which only support COM-based add-ins.

Some months ago I wrote how to create a COM add-in for the VBA editor of Office using .NET, which is almost the only way to create an add-in for the VBA editor of Office 64-bit, since it doesn’t support 32-bit COM add-ins.

I am working since some months ago on a .NET-based version of my MZ-Tools add-in for the VBA editor of Office 32/64-bit and I always wanted a single script to perform the two steps above. This was a nice excuse to learn Windows PowerShell, so I bought a book and after reading some chapters to get the concepts today I decided to create the scripts that call regasm.exe to register the .Net assembly for COM-Interop and create the registry entries for the add-in to be recognized by the VBA editor:

1) This is the content of a file named Functions.ps1 which contains reusable functions:

# To run .ps1 scripts you need to execute first: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$Regasm32 = 'C:\Windows\Microsoft.NET\Framework\v2.0.50727\regasm.exe'
$Regasm64 = 'C:\Windows\Microsoft.NET\Framework64\v2.0.50727\regasm.exe'

function Register-Assembly32([string]$Assembly, [string]$RegistryKey, [string]$FriendlyName)
{
   Execute-Command -RegAsm $Regasm32  -Arguments '/codebase' -Assembly $Assembly
   Register-AddIn -RegistryKey $RegistryKey -FriendlyName $FriendlyName
}

function Register-Assembly64([string]$Assembly, [string]$RegistryKey, [string]$FriendlyName)
{
   Execute-Command -RegAsm $Regasm64 -Arguments '/codebase' -Assembly $Assembly
   Register-AddIn -RegistryKey $RegistryKey -FriendlyName $FriendlyName
}

function Unregister-Assembly32([string]$Assembly, [string]$RegistryKey)
{
   Execute-Command -RegAsm $Regasm32 -Arguments '/unregister' -Assembly $Assembly
   Unregister-AddIn -RegistryKey $RegistryKey
}

function Unregister-Assembly64([string]$Assembly, [string]$RegistryKey)
{
   Execute-Command -RegAsm $RegAsm64 -Arguments '/unregister' -Assembly $Assembly
   Unregister-AddIn -RegistryKey $RegistryKey
}

function Register-AddIn([string]$RegistryKey, [string]$FriendlyName)
{
   New-Item         -Path $RegistryKey -Force
   New-ItemProperty -Path $RegistryKey -Name Description  -PropertyType String -Value $FriendlyName
   New-ItemProperty -Path $RegistryKey -Name FriendlyName -PropertyType String -Value $FriendlyName
   New-ItemProperty -Path $RegistryKey -Name LoadBehavior -PropertyType DWord  -Value 3
}

function Unregister-AddIn([string]$RegistryKey)
{
   if (Test-Path -Path $RegistryKey)
   {
      Remove-Item -Path $RegistryKey
   }
}

function Execute-Command([string]$RegAsm, [string]$Arguments, [string]$Assembly)
{
   $psi = New-Object System.Diagnostics.ProcessStartInfo
   $psi.CreateNoWindow = $true
   $psi.UseShellExecute = $false
   $psi.RedirectStandardOutput = $true
   $psi.RedirectStandardError = $true
   $psi.FileName = $RegAsm
   $psi.Arguments = $Arguments + ' ' + $Assembly
   $process = New-Object System.Diagnostics.Process
   $process.StartInfo = $psi
   [void]$process.Start()
   $StandardOutput = $process.StandardOutput.ReadToEnd()
   $StandardError = $process.StandardError.ReadToEnd()
   $process.WaitForExit()
   [system.windows.forms.messagebox]::show($StandardOutput + $StandardError)
}

2) Then I have other scripts that include that script:

MyAddInVBA32Registration.ps1:

$ScriptDirectory = Split-Path $MyInvocation.MyCommand.Path
. (Join-Path $ScriptDirectory Functions.ps1)

$Assembly = (get-item Env:USERPROFILE).Value + 'Documents\MyAddIn\Exe\Debug\MyAddIn.dll'

Register-Assembly32 -Assembly $Assembly -RegistryKey 'HKCU:Software\Microsoft\VBA\VBE\6.0\AddIns\MyAddIn.Connect' -FriendlyName 'My Add-In'

MyAddInVBA64Registration.ps1:

$ScriptDirectory = Split-Path $MyInvocation.MyCommand.Path
. (Join-Path $ScriptDirectory Functions.ps1)

$Assembly = (get-item Env:USERP2ROFILE).Value + 'Documents\MyAddIn\Exe\Debug\MyAddIn.dll'

Register-Assembly64 -Assembly $Assembly -RegistryKey 'HKCU:Software\Microsoft\VBA\VBE\6.0\AddIns64\MyAddIn.Connect' -FriendlyName 'My Add-In'

MyAddInVBA32Unregistration.ps1:

$ScriptDirectory = Split-Path $MyInvocation.MyCommand.Path
. (Join-Path $ScriptDirectory Functions.ps1)

$Assembly = (get-item Env:USERPROFILE).Value + 'Documents\MyAddIn\Exe\Debug\MyAddIn.dll'

Unregister-Assembly32 -Assembly $Assembly -RegistryKey 'HKCU:Software\Microsoft\VBA\VBE\6.0\AddIns\MyAddIn.Connect'

MyAddInVBA64Unregistration.ps1:

$ScriptDirectory = Split-Path $MyInvocation.MyCommand.Path
. (Join-Path $ScriptDirectory Functions.ps1)

$Assembly = (get-item Env:USERPROFILE).Value + 'Documents\MyAddIn\Exe\Debug\MyAddIn.dll'

Unregister-Assembly64 -Assembly $Assembly -RegistryKey 'HKCU:Software\Microsoft\VBA\VBE\6.0\AddIns64\MyAddIn.Connect'

To run the scripts you need to enable PowerShell execution first and they need to be run with admin rights.

I am finding PowerShell with a learning curve harder than expected and with some “by-design” issues that makes it “tricky” in my opinion, but I hope to learn it in depth.

2 thoughts on “Windows PowerShell scripts to register a .NET-based add-in for a COM-based host application”

  1. Is there a means to do this with Straight up dll’s that regasm uses? What I mean is reflect in the same dll’s that regasm uses? for use in a PS script?

  2. Hi Thom,

    Regasm.exe internally uses:

    [DllImport(“oleaut32.dll”, CharSet=CharSet.Unicode, PreserveSig=false)]
    private static extern void LoadTypeLibEx(string strTypeLibName, REGKIND regKind, out ITypeLib TypeLib);

    [DllImport(“oleaut32.dll”, CharSet=CharSet.Unicode, PreserveSig=false)]
    private static extern void RegisterTypeLib(ITypeLib TypeLib, string szFullPath, string szHelpDirs);

    You can use .NET Reflector to decompile the internals of regasm.exe and see how it works.

Comments are closed.