├── 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 |
--------------------------------------------------------------------------------