├── Source ├── Test │ ├── ExtraFiles │ │ ├── File1.txt │ │ ├── MoreExtraFiles │ │ │ ├── File3.txt │ │ │ └── File4.txt │ │ └── GUI │ │ │ └── GuiRibbon │ │ │ └── AnotherFile.txt │ ├── File.txt │ ├── Functions.cs │ ├── Properties │ │ └── launchSettings.json │ └── Test.csproj ├── ExcelAddInBundle │ ├── Resources │ │ ├── Icon.ico │ │ └── EULA.rtf │ ├── ExcelAddInBundle.wixproj │ └── Bundle.wxs ├── ExcelAddInDeploy │ ├── Resources │ │ ├── Icon.ico │ │ ├── Banner.jpg │ │ ├── Dialog.jpg │ │ └── EULA.rtf │ ├── EnglishLoc.wxl │ ├── CustomMessages.wxl │ ├── ExcelAddInDeploy.wixproj │ ├── Generate-ExtrafilesWxs.ps1 │ └── Product.wxs ├── InstallerCA │ ├── CustomAction.config │ ├── WindowWrapper.cs │ ├── ClosePromptForm.cs │ ├── ClosePromptForm.designer.cs │ ├── PromptCloseApplication.cs │ ├── InstallerCA.csproj │ └── CustomAction.cs └── Installer.sln ├── .gitattributes ├── License.md ├── Readme.md └── .gitignore /Source/Test/ExtraFiles/File1.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Source/Test/File.txt: -------------------------------------------------------------------------------- 1 | This is a test file. -------------------------------------------------------------------------------- /Source/Test/ExtraFiles/MoreExtraFiles/File3.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Source/Test/ExtraFiles/MoreExtraFiles/File4.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Source/Test/ExtraFiles/GUI/GuiRibbon/AnotherFile.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Source/ExcelAddInBundle/Resources/Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Excel-DNA/WiXInstaller/HEAD/Source/ExcelAddInBundle/Resources/Icon.ico -------------------------------------------------------------------------------- /Source/ExcelAddInDeploy/Resources/Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Excel-DNA/WiXInstaller/HEAD/Source/ExcelAddInDeploy/Resources/Icon.ico -------------------------------------------------------------------------------- /Source/ExcelAddInDeploy/Resources/Banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Excel-DNA/WiXInstaller/HEAD/Source/ExcelAddInDeploy/Resources/Banner.jpg -------------------------------------------------------------------------------- /Source/ExcelAddInDeploy/Resources/Dialog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Excel-DNA/WiXInstaller/HEAD/Source/ExcelAddInDeploy/Resources/Dialog.jpg -------------------------------------------------------------------------------- /Source/InstallerCA/CustomAction.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Source/Test/Functions.cs: -------------------------------------------------------------------------------- 1 | namespace Test 2 | { 3 | public static class Functions 4 | { 5 | public static object SayHello() => "Hello from the installer test add-in"; 6 | // public static object SayHello2() => "Hello from the installer test add-in"; 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /Source/Test/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Excel": { 4 | "commandName": "Executable", 5 | "executablePath": "C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE", 6 | "commandLineArgs": "/x \"Test-AddIn64.xll\"" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /Source/InstallerCA/WindowWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace InstallerCA 5 | { 6 | public class WindowWrapper : IWin32Window { 7 | public WindowWrapper(IntPtr handle) { 8 | _hwnd = handle; 9 | } 10 | 11 | public IntPtr Handle { 12 | get { return _hwnd; } 13 | } 14 | 15 | private readonly IntPtr _hwnd; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Source/InstallerCA/ClosePromptForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace InstallerCA 5 | { 6 | public partial class ClosePromptForm : Form { 7 | public ClosePromptForm(string text) { 8 | InitializeComponent(); 9 | messageText.Text = text; 10 | } 11 | 12 | private void OkButtonClick(object sender, EventArgs e) { 13 | DialogResult = DialogResult.OK; 14 | Close(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Source/ExcelAddInBundle/ExcelAddInBundle.wixproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Bundle 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /Source/ExcelAddInBundle/Resources/EULA.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033\deflangfe1033{\fonttbl{\f0\fswiss\fprq2\fcharset0 Arial;}} 2 | {\*\generator Riched20 6.2.9200}{\*\mmathPr\mdispDef1\mwrapIndent1440 }\viewkind4\uc1 3 | \pard\nowidctlpar\f0\fs20 TODO: Place EULA Text Here. \par 4 | \par 5 | Special Note:\par 6 | \par 7 | The Windows Installer ScrollableText control is very old and is limited in the Rich Text that it can render. It is recommended to use the oldest version of Wordpad that you have to create this file and to test the installer on all of your platforms to ensure the EULA is readable.\par 8 | } 9 | -------------------------------------------------------------------------------- /Source/ExcelAddInDeploy/Resources/EULA.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033\deflangfe1033{\fonttbl{\f0\fswiss\fprq2\fcharset0 Arial;}} 2 | {\*\generator Riched20 6.2.9200}{\*\mmathPr\mdispDef1\mwrapIndent1440 }\viewkind4\uc1 3 | \pard\nowidctlpar\f0\fs20 TODO: Place EULA Text Here. \par 4 | \par 5 | Special Note:\par 6 | \par 7 | The Windows Installer ScrollableText control is very old and is limited in the Rich Text that it can render. It is recommended to use the oldest version of Wordpad that you have to create this file and to test the installer on all of your platforms to ensure the EULA is readable.\par 8 | } 9 | -------------------------------------------------------------------------------- /Source/ExcelAddInDeploy/EnglishLoc.wxl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Source/ExcelAddInBundle/Bundle.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Source/Test/Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net472 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | PreserveNewest 14 | 15 | 16 | PreserveNewest 17 | 18 | 19 | PreserveNewest 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2014 Excel-DNA Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Excel-DNA - WiXInstaller 2 | ------------------------ 3 | 4 | This is based on a user-contributed template (thank you very much to Lee Zeitz!) for making a WiX-based installer for an Excel-DNA add-in. 5 | 6 | Use this project as a template - fork it and update from there. 7 | The Test add-in is just as a sample - the add-in project need not be part of the installer solution. 8 | 9 | * You need to install the free “HeatWave” extension for Visual Studio from FireGiant (this guys whosupport WiX) - https://www.firegiant.com/docs/heatwave/. This is developed by the same people who made the WiX toolset, and they also provide some commercial products and support around it, like this VS extension. 10 | * Make your own fork of this repository 11 | * Update the information in the ExcelAddInDeploy project: 12 | * EnglishLoc.wxl - the strings for the installer to be fixed up 13 | * Product.wxs - set the ProductCode and UpgradeCode to be unique for your add-in, and the other Guids and add-in paths - look for the TODOs in this file 14 | * Fix the Resources - Banner, Icon end EULA, as required. 15 | 16 | Version for making a Local Machine installer using Active Setup 17 | --- 18 | 19 | Benoit Patra has created a version of the Excel-DNA WiX installer that uses the Active Setup feature in Windows to install for all users. The repository is here: https://github.com/bpatra/ExcelDNAWixInstallerLM and a very detailed write-up here: http://benoitpatra.com/2014/07/26/a-sample-wix-installer-using-the-activesetup-feature/ 20 | 21 | Any help to merge the installers, making the per-user or per-machine installations an installation option, would be greatly appreciated. 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Source/ExcelAddInDeploy/CustomMessages.wxl: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Source/ExcelAddInDeploy/ExcelAddInDeploy.wixproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | x86 9 | false 10 | ICE91 11 | 12 | 13 | 14 | InstallerCA 15 | {f135d7b8-747c-4c4f-a9fc-1f3a25fbd403} 16 | True 17 | True 18 | Binaries;Content;Satellites 19 | INSTALLFOLDER 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | $([System.IO.Path]::GetFullPath('$(SolutionDir)Test\bin\$(Configuration)\net472\publish')) 44 | $(DefineConstants);AddinPublishDir=$(AddinPublishDir) 45 | Software\MyCompany\MyProduct\ExtraFiles 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Source/InstallerCA/ClosePromptForm.designer.cs: -------------------------------------------------------------------------------- 1 | namespace InstallerCA 2 | { 3 | 4 | partial class ClosePromptForm { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) { 15 | if (disposing && (components != null)) { 16 | components.Dispose(); 17 | } 18 | base.Dispose(disposing); 19 | } 20 | 21 | #region Windows Form Designer generated code 22 | 23 | /// 24 | /// Required method for Designer support - do not modify 25 | /// the contents of this method with the code editor. 26 | /// 27 | private void InitializeComponent() { 28 | this.okButton = new System.Windows.Forms.Button(); 29 | this.messageText = new System.Windows.Forms.Label(); 30 | this.SuspendLayout(); 31 | // 32 | // okButton 33 | // 34 | this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; 35 | this.okButton.Location = new System.Drawing.Point(200, 67); 36 | this.okButton.Name = "okButton"; 37 | this.okButton.Size = new System.Drawing.Size(75, 23); 38 | this.okButton.TabIndex = 0; 39 | this.okButton.Text = "OK"; 40 | this.okButton.UseVisualStyleBackColor = true; 41 | this.okButton.Click += new System.EventHandler(this.OkButtonClick); 42 | // 43 | // messageText 44 | // 45 | this.messageText.AutoSize = true; 46 | this.messageText.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 47 | this.messageText.Location = new System.Drawing.Point(12, 25); 48 | this.messageText.MaximumSize = new System.Drawing.Size(460, 80); 49 | this.messageText.Name = "messageText"; 50 | this.messageText.Size = new System.Drawing.Size(0, 13); 51 | this.messageText.TabIndex = 1; 52 | // 53 | // ClosePromptForm 54 | // 55 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 56 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 57 | this.ClientSize = new System.Drawing.Size(474, 102); 58 | this.Controls.Add(this.messageText); 59 | this.Controls.Add(this.okButton); 60 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 61 | this.MaximizeBox = false; 62 | this.MinimizeBox = false; 63 | this.Name = "ClosePromptForm"; 64 | this.ShowIcon = false; 65 | this.ShowInTaskbar = false; 66 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 67 | this.Text = "Application needs to be closed"; 68 | this.ResumeLayout(false); 69 | this.PerformLayout(); 70 | 71 | } 72 | 73 | #endregion 74 | 75 | private System.Windows.Forms.Button okButton; 76 | private System.Windows.Forms.Label messageText; 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /Source/InstallerCA/PromptCloseApplication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.InteropServices; 4 | using System.Windows.Forms; 5 | 6 | namespace InstallerCA 7 | { 8 | public class PromptCloseApplication : IDisposable 9 | { 10 | #region Instance Variables 11 | private readonly string m_szProductName; 12 | private readonly string m_szProcessName; 13 | private readonly string m_szDisplayName; 14 | private System.Threading.Timer m_timer; 15 | private Form m_form; 16 | private IntPtr m_mainWindowHanle; 17 | 18 | [DllImport("user32.dll", SetLastError = true)] 19 | public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); 20 | #endregion 21 | 22 | #region Constructor 23 | public PromptCloseApplication(string productName, string processName, string displayName) 24 | { 25 | m_szProductName = productName; 26 | m_szProcessName = processName; 27 | m_szDisplayName = displayName; 28 | } 29 | #endregion 30 | 31 | #region Prompt 32 | public bool Prompt() 33 | { 34 | bool bReturn = false; 35 | bool bRunning = IsRunning(m_szProcessName); 36 | 37 | if (IsRunning(m_szProcessName)) 38 | { 39 | m_form = new ClosePromptForm(String.Format("Please close running instances of {0} before running {1} setup.", m_szDisplayName, m_szProductName)); 40 | m_mainWindowHanle = FindWindow(null, m_szProductName + " Setup"); 41 | if (m_mainWindowHanle == IntPtr.Zero) 42 | { 43 | m_mainWindowHanle = FindWindow("#32770", m_szProductName); 44 | } 45 | 46 | m_timer = new System.Threading.Timer(TimerElapsed, m_form, 200, 200); 47 | 48 | bReturn = ShowDialog(); 49 | } 50 | else 51 | { 52 | bReturn = true; 53 | } 54 | return bReturn; 55 | } 56 | #endregion 57 | 58 | #region ShowDialog 59 | bool ShowDialog() 60 | { 61 | bool bReturn = false; 62 | 63 | if (m_form.ShowDialog(new WindowWrapper(m_mainWindowHanle)) == DialogResult.OK) 64 | { 65 | bReturn = !IsRunning(m_szProcessName) || ShowDialog(); 66 | } 67 | return bReturn; 68 | } 69 | #endregion 70 | 71 | #region TimerElapsed 72 | private void TimerElapsed(object sender) 73 | { 74 | if (m_form == null || IsRunning(m_szProcessName) || !m_form.Visible) 75 | { 76 | return; 77 | } 78 | m_form.DialogResult = DialogResult.OK; 79 | m_form.Close(); 80 | } 81 | #endregion 82 | 83 | #region IsRunning 84 | private bool IsRunning(string processName) 85 | { 86 | bool bReturn = false; 87 | Process[] procList = Process.GetProcesses(); 88 | foreach (Process p in procList) 89 | { 90 | if (p.ProcessName.ToUpper() == processName.ToUpper()) 91 | { 92 | return true; 93 | } 94 | } 95 | return bReturn; 96 | } 97 | #endregion 98 | 99 | #region Destructor 100 | public void Dispose() 101 | { 102 | if (m_timer != null) 103 | { 104 | m_timer.Dispose(); 105 | } 106 | if (m_form != null && m_form.Visible) 107 | { 108 | m_form.Close(); 109 | } 110 | } 111 | #endregion 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | 37 | ## Ignore Visual Studio temporary files, build results, and 38 | ## files generated by popular Visual Studio add-ons. 39 | 40 | # User-specific files 41 | .vs/ 42 | *.suo 43 | *.user 44 | *.sln.docstates 45 | *.xll 46 | 47 | # Build results 48 | 49 | [Dd]ebug/ 50 | [Rr]elease/ 51 | [Dd]ebug64/ 52 | [Rr]elease64/ 53 | x64/ 54 | build/ 55 | [Bb]in/ 56 | [Oo]bj/ 57 | Package/nupkg/ 58 | 59 | # Ignore NuGet Packages 60 | *.nupkg 61 | **/packages/* 62 | 63 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 64 | !packages/*/build/ 65 | !Package/*/build/ 66 | 67 | # MSTest test Results 68 | [Tt]est[Rr]esult*/ 69 | [Bb]uild[Ll]og.* 70 | 71 | *_i.c 72 | *_p.c 73 | *.ilk 74 | *.meta 75 | *.obj 76 | *.pch 77 | *.pgc 78 | *.pgd 79 | *.rsp 80 | *.sbr 81 | *.tlb 82 | *.tli 83 | *.tlh 84 | *.tmp 85 | *.tmp_proj 86 | *.log 87 | *.vspscc 88 | *.vssscc 89 | .builds 90 | *.pidb 91 | *.log 92 | *.scc 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opensdf 99 | *.sdf 100 | *.cachefile 101 | *.VC.opendb 102 | *.VC.db 103 | 104 | # Visual Studio profiler 105 | *.psess 106 | *.vsp 107 | *.vspx 108 | # Guidance Automation Toolkit 109 | *.gpState 110 | 111 | # ReSharper is a .NET coding add-in 112 | _ReSharper*/ 113 | *.[Rr]e[Ss]harper 114 | 115 | # TeamCity is a build add-in 116 | _TeamCity* 117 | 118 | # DotCover is a Code Coverage Tool 119 | *.dotCover 120 | 121 | # NCrunch 122 | *.ncrunch* 123 | .*crunch*.local.xml 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.Publish.xml 143 | *.pubxml 144 | 145 | # NuGet Packages Directory 146 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 147 | #packages/ 148 | 149 | # Windows Azure Build Output 150 | csx 151 | *.build.csdef 152 | 153 | # Windows Store app package directory 154 | AppPackages/ 155 | 156 | # Others 157 | sql/ 158 | *.Cache 159 | ClientBin/ 160 | [Ss]tyle[Cc]op.* 161 | ~$* 162 | *~ 163 | *.dbmdl 164 | *.[Pp]ublish.xml 165 | *.pfx 166 | *.publishsettings 167 | 168 | # RIA/Silverlight projects 169 | Generated_Code/ 170 | 171 | # Backup & report files from converting an old project file to a newer 172 | # Visual Studio version. Backup files are not needed, because we have git ;-) 173 | _UpgradeReport_Files/ 174 | Backup*/ 175 | UpgradeLog*.XML 176 | UpgradeLog*.htm 177 | 178 | # SQL Server files 179 | App_Data/*.mdf 180 | App_Data/*.ldf 181 | 182 | ############# 183 | ## Windows detritus 184 | ############# 185 | 186 | # Windows image file caches 187 | Thumbs.db 188 | ehthumbs.db 189 | 190 | # Folder config file 191 | Desktop.ini 192 | 193 | # Recycle Bin used on file shares 194 | $RECYCLE.BIN/ 195 | 196 | # Mac crap 197 | .DS_Store 198 | 199 | 200 | ############# 201 | ## Python 202 | ############# 203 | 204 | *.py[co] 205 | 206 | # Packages 207 | *.egg 208 | *.egg-info 209 | dist/ 210 | build/ 211 | eggs/ 212 | parts/ 213 | var/ 214 | sdist/ 215 | develop-eggs/ 216 | .installed.cfg 217 | 218 | # Installer logs 219 | pip-log.txt 220 | 221 | # Unit test / coverage reports 222 | .coverage 223 | .tox 224 | 225 | #Translations 226 | *.mo 227 | 228 | #Mr Developer 229 | .mr.developer.cfg 230 | -------------------------------------------------------------------------------- /Source/InstallerCA/InstallerCA.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net462 5 | true 6 | 7 | Sample managed custom actions 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Source/ExcelAddInDeploy/Generate-ExtrafilesWxs.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [string]$PublishDir, 3 | [string]$OutputWxs, 4 | [string]$RootDirId = "AddinFolder", 5 | [string]$RegistryRoot = "HKCU", 6 | [string]$RegKeyBase = "Software\MyCompany\MyProduct\ExtraFiles" 7 | ) 8 | 9 | # === Stable GUID helper === 10 | function Get-StableGuid { 11 | param([string]$InputString) 12 | $md5 = [System.Security.Cryptography.MD5]::Create() 13 | $bytes = [System.Text.Encoding]::UTF8.GetBytes($InputString) 14 | $hash = $md5.ComputeHash($bytes) 15 | $hex = [System.BitConverter]::ToString($hash).Replace("-", "") 16 | "{0}-{1}-{2}-{3}-{4}" -f ` 17 | $hex.Substring(0, 8), 18 | $hex.Substring(8, 4), 19 | $hex.Substring(12, 4), 20 | $hex.Substring(16, 4), 21 | $hex.Substring(20, 12) 22 | } 23 | 24 | # === Setup XML === 25 | $namespace = "http://wixtoolset.org/schemas/v4/wxs" 26 | $xml = New-Object System.Xml.XmlDocument 27 | $xml.AppendChild($xml.CreateXmlDeclaration("1.0","UTF-8",$null)) | Out-Null 28 | $wix = $xml.CreateElement("Wix", $namespace) 29 | $xml.AppendChild($wix) | Out-Null 30 | 31 | # === Directories === 32 | $fragment1 = $xml.CreateElement("Fragment", $namespace) 33 | $wix.AppendChild($fragment1) | Out-Null 34 | $dirRef = $xml.CreateElement("DirectoryRef", $namespace) 35 | $dirRef.SetAttribute("Id", $RootDirId) 36 | $fragment1.AppendChild($dirRef) | Out-Null 37 | 38 | function New-DirectoryNode { 39 | param( 40 | [System.IO.DirectoryInfo]$Directory, 41 | [System.Xml.XmlElement]$ParentXmlElement 42 | ) 43 | foreach ($subDir in $Directory.GetDirectories()) { 44 | $relPath = $subDir.FullName.Substring($PublishDir.Length).Trim("\") 45 | $dirId = ($relPath -replace '[\\\/]', '_') + "_DIR" 46 | $dirName = $subDir.Name 47 | 48 | $dirElement = $xml.CreateElement("Directory", $namespace) 49 | $dirElement.SetAttribute("Id", $dirId) 50 | $dirElement.SetAttribute("Name", $dirName) 51 | $ParentXmlElement.AppendChild($dirElement) | Out-Null 52 | 53 | New-DirectoryNode -Directory $subDir -ParentXmlElement $dirElement 54 | } 55 | } 56 | 57 | New-DirectoryNode -Directory (Get-Item $PublishDir) -ParentXmlElement $dirRef 58 | 59 | # === Components === 60 | $fragment2 = $xml.CreateElement("Fragment", $namespace) 61 | $wix.AppendChild($fragment2) | Out-Null 62 | $components = $xml.CreateElement("ComponentGroup", $namespace) 63 | $components.SetAttribute("Id", "ExtraDistributables") 64 | $fragment2.AppendChild($components) | Out-Null 65 | 66 | # === Get directories and files === 67 | $dirGroups = @{} 68 | 69 | # Group all files by directory 70 | Get-ChildItem -Path $PublishDir -Recurse -File | Where-Object { $_.Extension -ne ".xll" } | ForEach-Object { 71 | $relDir = if ($_.Directory.FullName -eq $PublishDir) { "" } else { $_.DirectoryName.Substring($PublishDir.Length).Trim("\") } 72 | if (-not $dirGroups.ContainsKey($relDir)) { $dirGroups[$relDir] = @() } 73 | $dirGroups[$relDir] += $_ 74 | } 75 | 76 | # Ensure ALL directories get an entry, even if empty 77 | Get-ChildItem -Path $PublishDir -Recurse -Directory | ForEach-Object { 78 | $relDir = $_.FullName.Substring($PublishDir.Length).Trim("\") 79 | if (-not $dirGroups.ContainsKey($relDir)) { $dirGroups[$relDir] = @() } 80 | } 81 | # Also add root directory explicitly 82 | if (-not $dirGroups.ContainsKey("")) { $dirGroups[""] = @() } 83 | 84 | # === Emit Components === 85 | foreach ($kvp in $dirGroups.GetEnumerator()) { 86 | $relDirPath = $kvp.Key 87 | $filesInDir = $kvp.Value 88 | 89 | $dirId = if ($relDirPath -eq "") { $RootDirId } else { ($relDirPath -replace '[\\\/]', '_') + "_DIR" } 90 | $stableGuidInput = if ($relDirPath -ne "") { $relDirPath } else { "_root_" } 91 | $componentGuid = Get-StableGuid $stableGuidInput 92 | 93 | $compElement = $xml.CreateElement("Component", $namespace) 94 | $compElement.SetAttribute("Guid", "{$componentGuid}") 95 | $compElement.SetAttribute("Directory", $dirId) 96 | 97 | # Always include a RegistryValue for KeyPath 98 | $reg = $xml.CreateElement("RegistryValue", $namespace) 99 | $regKey = $RegKeyBase 100 | if ($relDirPath -ne "") { 101 | $relClean = ($relDirPath -replace '^[\\\/]+', '').Replace('/', '\').Replace('\\', '\') 102 | $regKey += "\" + $relClean 103 | } 104 | $reg.SetAttribute("Root", $RegistryRoot) 105 | $reg.SetAttribute("Key", $regKey) 106 | $reg.SetAttribute("Name", "Installed") 107 | $reg.SetAttribute("Type", "string") 108 | $reg.SetAttribute("Value", "1") 109 | $reg.SetAttribute("KeyPath", "yes") 110 | $compElement.AppendChild($reg) | Out-Null 111 | 112 | # Add files and RemoveFile for each file with unique ID 113 | foreach ($file in $filesInDir) { 114 | $relPath = $file.FullName.Substring($PublishDir.Length).Trim("\") 115 | $safeRelPath = ($relPath -replace '[\\\/]', '_') 116 | 117 | $fileElem = $xml.CreateElement("File", $namespace) 118 | $fileElem.SetAttribute("Id", "Fil_" + $safeRelPath) 119 | $fileElem.SetAttribute("Source", $file.FullName) 120 | $compElement.AppendChild($fileElem) | Out-Null 121 | 122 | $removeElem = $xml.CreateElement("RemoveFile", $namespace) 123 | $removeElem.SetAttribute("Id", "Remove_" + $safeRelPath) # Use rel path for uniqueness 124 | $removeElem.SetAttribute("Name", $file.Name) 125 | $removeElem.SetAttribute("On", "uninstall") 126 | $compElement.AppendChild($removeElem) | Out-Null 127 | } 128 | 129 | # If root dir: do not add RemoveFolder for root 130 | if ($relDirPath -ne "") { 131 | # Subdirectories only get RemoveFolder 132 | $removeFolder = $xml.CreateElement("RemoveFolder", $namespace) 133 | $safeDirId = if ($relDirPath -eq "") { "_root_" } else { ($relDirPath -replace '[\\\/]', '_') } 134 | $removeFolder.SetAttribute("Id", "RemoveFolder_" + $safeDirId) 135 | $removeFolder.SetAttribute("Directory", $dirId) 136 | $removeFolder.SetAttribute("On", "uninstall") 137 | $compElement.AppendChild($removeFolder) | Out-Null 138 | } 139 | 140 | $components.AppendChild($compElement) | Out-Null 141 | } 142 | 143 | $xml.Save($OutputWxs) 144 | Write-Host "✅ Wrote $OutputWxs to $OutputWxs" 145 | -------------------------------------------------------------------------------- /Source/Installer.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33516.290 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InstallerCA", "InstallerCA\InstallerCA.csproj", "{F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}" 7 | EndProject 8 | Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "ExcelAddInDeploy", "ExcelAddInDeploy\ExcelAddInDeploy.wixproj", "{9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "Test\Test.csproj", "{95690D96-9DFD-4E02-AD90-BEC50017767D}" 11 | EndProject 12 | Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "ExcelAddInBundle", "ExcelAddInBundle\ExcelAddInBundle.wixproj", "{32CD7165-0E3E-4728-8090-A865DA4F4402}" 13 | ProjectSection(ProjectDependencies) = postProject 14 | {95690D96-9DFD-4E02-AD90-BEC50017767D} = {95690D96-9DFD-4E02-AD90-BEC50017767D} 15 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403} = {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403} 16 | EndProjectSection 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Debug|ARM64 = Debug|ARM64 22 | Debug|Mixed Platforms = Debug|Mixed Platforms 23 | Debug|x64 = Debug|x64 24 | Debug|x86 = Debug|x86 25 | Release|Any CPU = Release|Any CPU 26 | Release|ARM64 = Release|ARM64 27 | Release|Mixed Platforms = Release|Mixed Platforms 28 | Release|x64 = Release|x64 29 | Release|x86 = Release|x86 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Debug|ARM64.ActiveCfg = Debug|Any CPU 35 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Debug|ARM64.Build.0 = Debug|Any CPU 36 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 37 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 38 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Debug|x64.ActiveCfg = Debug|Any CPU 39 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Debug|x64.Build.0 = Debug|Any CPU 40 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Debug|x86.ActiveCfg = Debug|Any CPU 41 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Release|ARM64.ActiveCfg = Release|Any CPU 44 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Release|ARM64.Build.0 = Release|Any CPU 45 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 46 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Release|Mixed Platforms.Build.0 = Release|Any CPU 47 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Release|x64.ActiveCfg = Release|Any CPU 48 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Release|x64.Build.0 = Release|Any CPU 49 | {F135D7B8-747C-4C4F-A9FC-1F3A25FBD403}.Release|x86.ActiveCfg = Release|Any CPU 50 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Debug|Any CPU.ActiveCfg = Debug|x86 51 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Debug|ARM64.ActiveCfg = Debug|ARM64 52 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Debug|ARM64.Build.0 = Debug|ARM64 53 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 54 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Debug|Mixed Platforms.Build.0 = Debug|x86 55 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Debug|x64.ActiveCfg = Debug|x64 56 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Debug|x64.Build.0 = Debug|x64 57 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Debug|x86.ActiveCfg = Debug|x86 58 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Debug|x86.Build.0 = Debug|x86 59 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Release|Any CPU.ActiveCfg = Release|x86 60 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Release|ARM64.ActiveCfg = Release|ARM64 61 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Release|ARM64.Build.0 = Release|ARM64 62 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Release|Mixed Platforms.ActiveCfg = Release|x86 63 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Release|Mixed Platforms.Build.0 = Release|x86 64 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Release|x64.ActiveCfg = Release|x64 65 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Release|x64.Build.0 = Release|x64 66 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Release|x86.ActiveCfg = Release|x86 67 | {9787F6FE-2779-410B-B4EF-A04D2C6C7EEA}.Release|x86.Build.0 = Release|x86 68 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 69 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Debug|Any CPU.Build.0 = Debug|Any CPU 70 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Debug|ARM64.ActiveCfg = Debug|Any CPU 71 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Debug|ARM64.Build.0 = Debug|Any CPU 72 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 73 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 74 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Debug|x64.ActiveCfg = Debug|Any CPU 75 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Debug|x64.Build.0 = Debug|Any CPU 76 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Debug|x86.ActiveCfg = Debug|Any CPU 77 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Debug|x86.Build.0 = Debug|Any CPU 78 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Release|Any CPU.ActiveCfg = Release|Any CPU 79 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Release|Any CPU.Build.0 = Release|Any CPU 80 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Release|ARM64.ActiveCfg = Release|Any CPU 81 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Release|ARM64.Build.0 = Release|Any CPU 82 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 83 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Release|Mixed Platforms.Build.0 = Release|Any CPU 84 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Release|x64.ActiveCfg = Release|Any CPU 85 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Release|x64.Build.0 = Release|Any CPU 86 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Release|x86.ActiveCfg = Release|Any CPU 87 | {95690D96-9DFD-4E02-AD90-BEC50017767D}.Release|x86.Build.0 = Release|Any CPU 88 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Debug|Any CPU.ActiveCfg = Debug|x64 89 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Debug|Any CPU.Build.0 = Debug|x64 90 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Debug|ARM64.ActiveCfg = Debug|ARM64 91 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Debug|ARM64.Build.0 = Debug|ARM64 92 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 93 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Debug|Mixed Platforms.Build.0 = Debug|x86 94 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Debug|x64.ActiveCfg = Debug|x64 95 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Debug|x64.Build.0 = Debug|x64 96 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Debug|x86.ActiveCfg = Debug|x86 97 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Debug|x86.Build.0 = Debug|x86 98 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Release|Any CPU.ActiveCfg = Release|x64 99 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Release|Any CPU.Build.0 = Release|x64 100 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Release|ARM64.ActiveCfg = Release|ARM64 101 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Release|ARM64.Build.0 = Release|ARM64 102 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Release|Mixed Platforms.ActiveCfg = Release|x64 103 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Release|Mixed Platforms.Build.0 = Release|x64 104 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Release|x64.ActiveCfg = Release|x64 105 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Release|x64.Build.0 = Release|x64 106 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Release|x86.ActiveCfg = Release|x86 107 | {32CD7165-0E3E-4728-8090-A865DA4F4402}.Release|x86.Build.0 = Release|x86 108 | EndGlobalSection 109 | GlobalSection(SolutionProperties) = preSolution 110 | HideSolutionNode = FALSE 111 | EndGlobalSection 112 | GlobalSection(ExtensibilityGlobals) = postSolution 113 | SolutionGuid = {BF27ED09-3BB7-467A-BEF7-21EB5D960049} 114 | EndGlobalSection 115 | EndGlobal 116 | -------------------------------------------------------------------------------- /Source/ExcelAddInDeploy/Product.wxs: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /Source/InstallerCA/CustomAction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Globalization; 8 | using WixToolset.Dtf.WindowsInstaller; 9 | 10 | namespace InstallerCA 11 | { 12 | public class CustomActions 13 | { 14 | #region Methods 15 | 16 | #region CaRegisterAddIn 17 | [CustomAction] 18 | public static ActionResult CaRegisterAddIn(Session session) 19 | { 20 | string szOfficeRegKeyVersions = string.Empty; 21 | string szBaseAddInKey = @"Software\Microsoft\Office\"; 22 | string szXll32Bit = string.Empty; 23 | string szXll64Bit = string.Empty; 24 | string szXllToRegister = string.Empty; 25 | string szFolder = string.Empty; 26 | int nOpenVersion; 27 | double nVersion; 28 | bool bFoundOffice = false; 29 | List lstVersions; 30 | 31 | try 32 | { 33 | session.Log("Enter try block of CaRegisterAddIn"); 34 | 35 | szOfficeRegKeyVersions = session["OFFICEREGKEYS"]; 36 | szXll32Bit = session["XLL32"]; 37 | szXll64Bit = session["XLL64"]; 38 | szFolder = session["AddinFolder"]; 39 | session.Log($"CaRegisterAddIn Args: OFFICEREGKEYS={szOfficeRegKeyVersions}, XLL32={szXll32Bit}, XLL64={szXll64Bit}, szFolder={szFolder}"); 40 | 41 | szXll32Bit = Path.Combine(szFolder, szXll32Bit); 42 | szXll64Bit = Path.Combine(szFolder, szXll64Bit); 43 | 44 | if (szOfficeRegKeyVersions.Length > 0) 45 | { 46 | lstVersions = szOfficeRegKeyVersions.Split(',').ToList(); 47 | 48 | foreach (string szOfficeVersionKey in lstVersions) 49 | { 50 | nVersion = double.Parse(szOfficeVersionKey, NumberStyles.Any, CultureInfo.InvariantCulture); 51 | 52 | session.Log("Retrieving Registry Information for : " + szBaseAddInKey + szOfficeVersionKey); 53 | 54 | // get the OPEN keys from the Software\Microsoft\Office\[Version]\Excel\Options key, skip if office version not found. 55 | if (Registry.CurrentUser.OpenSubKey(szBaseAddInKey + szOfficeVersionKey, false) != null) 56 | { 57 | string szKeyName = szBaseAddInKey + szOfficeVersionKey + @"\Excel\Options"; 58 | 59 | szXllToRegister = GetAddInName(szXll32Bit, szXll64Bit, szOfficeVersionKey, nVersion); 60 | 61 | RegistryKey rkExcelXll = Registry.CurrentUser.OpenSubKey(szKeyName, true); 62 | 63 | if (szXllToRegister != string.Empty && rkExcelXll != null) 64 | { 65 | string[] szValueNames = rkExcelXll.GetValueNames(); 66 | bool bIsOpen = false; 67 | int nMaxOpen = -1; 68 | 69 | // check every value for OPEN keys 70 | foreach (string szValueName in szValueNames) 71 | { 72 | // if there are already OPEN keys, determine if our key is installed 73 | if (szValueName.StartsWith("OPEN")) 74 | { 75 | nOpenVersion = int.TryParse(szValueName.Substring(4), NumberStyles.Any, CultureInfo.InvariantCulture, out nOpenVersion) ? nOpenVersion : 0; 76 | int nNewOpen = szValueName == "OPEN" ? 0 : nOpenVersion; 77 | if (nNewOpen > nMaxOpen) 78 | { 79 | nMaxOpen = nNewOpen; 80 | } 81 | 82 | // if the key is our key, set the open flag 83 | //NOTE: this line means if the user has changed its office from 32 to 64 (or conversly) without removing the addin then we will not update the key properly 84 | //The user will have to uninstall addin before installing it again 85 | if (rkExcelXll.GetValue(szValueName).ToString().Contains(szXllToRegister)) 86 | { 87 | bIsOpen = true; 88 | } 89 | } 90 | } 91 | 92 | // if adding a new key 93 | if (!bIsOpen) 94 | { 95 | if (nMaxOpen == -1) 96 | { 97 | rkExcelXll.SetValue("OPEN", "/R \"" + szXllToRegister + "\""); 98 | } 99 | else 100 | { 101 | rkExcelXll.SetValue("OPEN" + (nMaxOpen + 1).ToString(), "/R \"" + szXllToRegister + "\""); 102 | } 103 | rkExcelXll.Close(); 104 | } 105 | bFoundOffice = true; 106 | } 107 | else 108 | { 109 | session.Log("Unable to retrieve key for : " + szKeyName); 110 | } 111 | } 112 | else 113 | { 114 | session.Log("Unable to retrieve registry Information for : " + szBaseAddInKey + szOfficeVersionKey); 115 | } 116 | } 117 | } 118 | 119 | session.Log("End CaRegisterAddIn"); 120 | } 121 | catch (System.Security.SecurityException ex) 122 | { 123 | session.Log("CaRegisterAddIn SecurityException" + ex.Message); 124 | bFoundOffice = false; 125 | } 126 | catch (System.UnauthorizedAccessException ex) 127 | { 128 | session.Log("CaRegisterAddIn UnauthorizedAccessException" + ex.Message); 129 | bFoundOffice = false; 130 | } 131 | catch (Exception ex) 132 | { 133 | session.Log("CaRegisterAddIn Exception" + ex.Message); 134 | bFoundOffice = false; 135 | } 136 | 137 | return bFoundOffice ? ActionResult.Success : ActionResult.Failure; 138 | } 139 | #endregion 140 | 141 | #region CaUnRegisterAddIn 142 | [CustomAction] 143 | public static ActionResult CaUnRegisterAddIn(Session session) 144 | { 145 | string szOfficeRegKeyVersions = string.Empty; 146 | string szBaseAddInKey = @"Software\Microsoft\Office\"; 147 | string szXll32Bit = string.Empty; 148 | string szXll64Bit = string.Empty; 149 | string szFolder = string.Empty; 150 | bool bFoundOffice = false; 151 | List lstVersions; 152 | 153 | try 154 | { 155 | session.Log("Begin CaUnRegisterAddIn"); 156 | 157 | szOfficeRegKeyVersions = session["OFFICEREGKEYS"]; 158 | szXll32Bit = session["XLL32"]; 159 | szXll64Bit = session["XLL64"]; 160 | szFolder = session["AddinFolder"]; 161 | session.Log($"CaUnRegisterAddIn AddInFolder={szFolder ?? ""}"); 162 | session.Log($"CaUnRegisterAddIn Args: OFFICEREGKEYS={szOfficeRegKeyVersions ?? ""}, XLL32={szXll32Bit ?? ""}, XLL64={szXll64Bit ?? ""}, szFolder={szFolder ?? ""}"); 163 | 164 | szXll32Bit = Path.Combine(szFolder, szXll32Bit); 165 | szXll64Bit = Path.Combine(szFolder, szXll64Bit); 166 | 167 | 168 | if (szOfficeRegKeyVersions.Length > 0) 169 | { 170 | lstVersions = szOfficeRegKeyVersions.Split(',').ToList(); 171 | 172 | foreach (string szOfficeVersionKey in lstVersions) 173 | { 174 | // only remove keys where office version is found 175 | if (Registry.CurrentUser.OpenSubKey(szBaseAddInKey + szOfficeVersionKey, false) != null) 176 | { 177 | bFoundOffice = true; 178 | 179 | string szKeyName = szBaseAddInKey + szOfficeVersionKey + @"\Excel\Options"; 180 | 181 | var rkAddInKey = Registry.CurrentUser.OpenSubKey(szKeyName, true); 182 | if (rkAddInKey == null) continue; 183 | 184 | var szValueNames = rkAddInKey.GetValueNames(); 185 | var allOpenKeyValues = new List(); 186 | 187 | foreach (string szValueName in szValueNames) 188 | { 189 | if (!szValueName.StartsWith("OPEN")) continue; 190 | 191 | string openValue = rkAddInKey.GetValue(szValueName)?.ToString() ?? ""; 192 | 193 | bool matchXll32 = !string.IsNullOrEmpty(szXll32Bit) && openValue.Contains(szXll32Bit); 194 | bool matchXll64 = !string.IsNullOrEmpty(szXll64Bit) && openValue.Contains(szXll64Bit); 195 | 196 | if (matchXll32 || matchXll64) 197 | { 198 | session.Log($"Deleting registry value '{szValueName}' because matchXll32={matchXll32}, matchXll64={matchXll64}"); 199 | // Do not add to the list — so it’s dropped. 200 | } 201 | else 202 | { 203 | session.Log($"Preserving OPEN value '{szValueName}' = '{openValue}'"); 204 | allOpenKeyValues.Add(openValue); 205 | } 206 | 207 | rkAddInKey.DeleteValue(szValueName); 208 | } 209 | 210 | session.Log($"Rewriting OPEN keys: {allOpenKeyValues.Count} remaining."); 211 | 212 | int i = 0; 213 | foreach (var openValue in allOpenKeyValues) 214 | { 215 | string keyName = i == 0 ? "OPEN" : $"OPEN{i}"; 216 | session.Log($"Rewriting {keyName} = '{openValue}'"); 217 | rkAddInKey.SetValue(keyName, openValue); 218 | i++; 219 | } 220 | } 221 | } 222 | } 223 | 224 | session.Log("End CaUnRegisterAddIn"); 225 | } 226 | catch (Exception ex) 227 | { 228 | session.Log($"CaUnRegisterAddIn Exception: {ex.Message}"); 229 | } 230 | 231 | return bFoundOffice ? ActionResult.Success : ActionResult.Failure; 232 | } 233 | #endregion 234 | 235 | #region ClosePrompt 236 | [CustomAction] 237 | public static ActionResult ClosePrompt(Session session) 238 | { 239 | session.Log("Begin PromptToCloseApplications"); 240 | try 241 | { 242 | var productName = session["ProductName"]; 243 | var processes = session["PromptToCloseProcesses"].Split(','); 244 | var displayNames = session["PromptToCloseDisplayNames"].Split(','); 245 | 246 | if (processes.Length != displayNames.Length) 247 | { 248 | session.Log(@"Please check that 'PromptToCloseProcesses' and 'PromptToCloseDisplayNames' exist and have same number of items."); 249 | return ActionResult.Failure; 250 | } 251 | 252 | for (var i = 0; i < processes.Length; i++) 253 | { 254 | session.Log("Prompting process {0} with name {1} to close.", processes[i], displayNames[i]); 255 | using (var prompt = new PromptCloseApplication(productName, processes[i], displayNames[i])) 256 | { 257 | if (!prompt.Prompt()) 258 | { 259 | return ActionResult.Failure; 260 | } 261 | } 262 | } 263 | } 264 | catch (Exception ex) 265 | { 266 | session.Log("Missing properties or wrong values. Please check that 'PromptToCloseProcesses' and 'PromptToCloseDisplayNames' exist and have same number of items. \nException:" + ex.Message); 267 | return ActionResult.Failure; 268 | } 269 | 270 | session.Log("End PromptToCloseApplications"); 271 | return ActionResult.Success; 272 | } 273 | #endregion 274 | 275 | #region GetAddInName 276 | public static string GetAddInName(string szXll32Name, string szXll64Name, string szOfficeVersionKey, double nVersion) 277 | { 278 | string szXllToRegister = string.Empty; 279 | 280 | if (nVersion >= 14) 281 | { 282 | // determine if office is 32-bit or 64-bit 283 | RegistryKey localMachineRegistry = // 64bit machines need to determine correct hive. 284 | RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, 285 | Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32); 286 | RegistryKey rkBitness = localMachineRegistry.OpenSubKey(@"Software\Microsoft\Office\" + szOfficeVersionKey + @"\Outlook", false); 287 | if (rkBitness != null) 288 | { 289 | object oBitValue = rkBitness.GetValue("Bitness"); 290 | if (oBitValue != null) 291 | { 292 | if (oBitValue.ToString() == "x64") 293 | { 294 | szXllToRegister = szXll64Name; 295 | } 296 | else 297 | { 298 | szXllToRegister = szXll32Name; 299 | } 300 | } 301 | else 302 | { 303 | szXllToRegister = szXll32Name; 304 | } 305 | } 306 | else 307 | { 308 | if (Environment.Is64BitOperatingSystem) 309 | { 310 | localMachineRegistry = //64bit machines need to check 32bit registry too! 311 | RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32); 312 | rkBitness = 313 | localMachineRegistry.OpenSubKey( 314 | @"Software\Microsoft\Office\" + szOfficeVersionKey + @"\Outlook", false); 315 | if (rkBitness != null) 316 | { 317 | var oBitValue = rkBitness.GetValue("Bitness"); 318 | if (oBitValue != null) 319 | { 320 | if (oBitValue.ToString() == "x64") 321 | { 322 | szXllToRegister = szXll64Name; 323 | } 324 | else 325 | { 326 | szXllToRegister = szXll32Name; 327 | } 328 | } 329 | else 330 | { 331 | szXllToRegister = szXll32Name; 332 | } 333 | } 334 | else 335 | { 336 | szXllToRegister = szXll32Name; 337 | } 338 | } 339 | else 340 | szXllToRegister = szXll32Name; 341 | } 342 | } 343 | else 344 | { 345 | szXllToRegister = szXll32Name; 346 | } 347 | 348 | return szXllToRegister; 349 | } 350 | #endregion 351 | 352 | #endregion 353 | } 354 | } 355 | --------------------------------------------------------------------------------