Skip to content

Commit 71d9fb5

Browse files
authored
1769 registrywow action (#1857)
* test * test * reg * RegistryAction * correct ProgramFiles location * OOPS * ubil * RegistryAction.uninstall
1 parent 88320b6 commit 71d9fb5

File tree

15 files changed

+935
-57
lines changed

15 files changed

+935
-57
lines changed

OneMoreSetup/OneMoreSetup.vdproj

Lines changed: 375 additions & 37 deletions
Large diffs are not rendered by default.

OneMoreSetupActions/Actions/ActiveSetupAction.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public override int Install()
3838

3939
try
4040
{
41-
var p = $@"Software\Microsoft\Active Setup\Installed Components\{RegistryHelper.OneNoteID}";
41+
var p = $@"Software\Microsoft\Active Setup\Installed Components\{RegistryHelper.OneMoreID}";
4242
logger.WriteLine($"opening key HKLM:\\{p}");
4343

4444
using var key = Registry.LocalMachine.OpenSubKey(p,

OneMoreSetupActions/Actions/CheckBitnessAction.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ public CheckBitnessAction(Logger logger, Stepper stepper, Architecture architect
2929
}
3030

3131

32+
public Architecture OneNoteArchitecture { get; private set; }
33+
34+
3235
public override int Install()
3336
{
3437
logger.WriteLine();
@@ -70,6 +73,8 @@ public override int Install()
7073
;
7174
}
7275

76+
OneNoteArchitecture = onarc;
77+
7378
if (!ok)
7479
{
7580
var msg = $"error: installer architecture ({urarc}) does not match OS ({osarc}) or OneNote ({onarc})";
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
;---------------------------------------------------------------
2+
; Configuration template for OneMore registration.
3+
;---------------------------------------------------------------
4+
Windows Registry Editor Version 5.00
5+
6+
[HKEY_CLASSES_ROOT\AppID\{OneMoreID}]
7+
"DllSurrogate"=""
8+
9+
[HKEY_CLASSES_ROOT\onemore]
10+
@="URL:OneMore Protocol Handler"
11+
"URL Protocol"=""
12+
13+
[HKEY_CLASSES_ROOT\onemore\shell]
14+
15+
[HKEY_CLASSES_ROOT\onemore\shell\open]
16+
17+
[HKEY_CLASSES_ROOT\onemore\shell\open\command]
18+
@="\"{ProgramFiles}\\River\\OneMoreAddIn\\OneMoreProtocolHandler.exe\" %1 %2 %3 %4 %5"
19+
20+
[HKEY_CLASSES_ROOT\River.OneMoreAddIn]
21+
@="River.OneMoreAddIn.AddIn"
22+
23+
[HKEY_CLASSES_ROOT\River.OneMoreAddIn\CLSID]
24+
@="{OneMoreID}"
25+
26+
[HKEY_CLASSES_ROOT\River.OneMoreAddIn\CurVer]
27+
@="River.OneMoreAddIn.1"
28+
29+
[HKEY_CLASSES_ROOT\River.OneMoreAddIn.1]
30+
@="Addin class"
31+
32+
[HKEY_CLASSES_ROOT\River.OneMoreAddIn.1\CLSID]
33+
@="{OneMoreID}"
34+
35+
[HKEY_CLASSES_ROOT\CLSID\{OneMoreID}]
36+
@="River.OneMoreAddIn.AddIn"
37+
"AppID"="{OneMoreID}"
38+
39+
[HKEY_CLASSES_ROOT\CLSID\{OneMoreID}\Implemented Categories]
40+
41+
[HKEY_CLASSES_ROOT\CLSID\{OneMoreID}\Implemented Categories\{62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}]
42+
43+
[HKEY_CLASSES_ROOT\CLSID\{OneMoreID}\InprocServer32]
44+
@="mscoree.dll"
45+
"ThreadingModel"="Both"
46+
"CodeBase"="{ProgramFiles}\\River\\OneMoreAddIn\\River.OneMoreAddIn.dll"
47+
"Class"="River.OneMoreAddIn.AddIn"
48+
"RuntimeVersion"="v4.0.30319"
49+
"Assembly"="River.OneMoreAddIn, Version={Version}, Culture=neutral, PublicKeyToken=null"
50+
51+
[HKEY_CLASSES_ROOT\CLSID\{OneMoreID}\InprocServer32\{Version}]
52+
"Assembly"="River.OneMoreAddIn, Version={Version}, Culture=neutral, PublicKeyToken=null"
53+
"CodeBase"="{ProgramFiles}\\River\\OneMoreAddIn\\River.OneMoreAddIn.dll"
54+
"RuntimeVersion"="v4.0.30319"
55+
"Class"="River.OneMoreAddIn.AddIn"
56+
57+
[HKEY_CLASSES_ROOT\CLSID\{OneMoreID}\ProgID]
58+
@="River.OneMoreAddin"
59+
60+
[HKEY_CLASSES_ROOT\CLSID\{OneMoreID}\Programmable]
61+
@=""
62+
63+
[HKEY_CLASSES_ROOT\CLSID\{OneMoreID}\TypeLib]
64+
@="{OneMoreID}"
65+
66+
[HKEY_CLASSES_ROOT\CLSID\{OneMoreID}\VersionIndependentProgID]
67+
@="River.OneMoreAddIn"
68+
69+
[HKEY_CURRENT_USER\SOFTWARE\Classes\AppID\{OneMoreID}]
70+
"DllSurrogate"=""
71+
72+
[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\OneNote\AddIns\River.OneMoreAddIn]
73+
"LoadBehavior"=dword:00000003
74+
"Description"="Extension for OneNote"
75+
"FriendlyName"="OneMoreAddIn"
76+
77+
[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\River.OneMoreAddIn.dll]
78+
"Path"="{ProgramFiles}\\River\\OneMoreAddIn\\River.OneMoreAddIn.dll"
79+
80+
;handled by TrustedProtocolAction
81+
;[HKEY_CURRENT_USER\SOFTWARE\Policies\Microsoft\Office\16.0\Common\Security\Trusted Protocols\All Applications\onemore:]
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
//************************************************************************************************
2+
// Copyright © 2025 Steven M Cohn. All rights reserved.
3+
//************************************************************************************************
4+
5+
namespace OneMoreSetupActions
6+
{
7+
using Microsoft.Win32;
8+
using System;
9+
using System.Runtime.InteropServices;
10+
using System.Text.RegularExpressions;
11+
12+
13+
/// <summary>
14+
/// Ensures the OneMore Registry settings are defined per the config file.
15+
/// </summary>
16+
internal class RegistryAction : CustomAction
17+
{
18+
private readonly Architecture architecture;
19+
20+
public RegistryAction(Logger logger, Stepper stepper, Architecture onArchitecture)
21+
: base(logger, stepper)
22+
{
23+
architecture = onArchitecture;
24+
}
25+
26+
27+
//========================================================================================
28+
29+
/// <summary>
30+
/// </summary>
31+
/// <returns></returns>
32+
public override int Install()
33+
{
34+
logger.WriteLine();
35+
logger.WriteLine($"RegistryAction.Install --- {architecture}");
36+
37+
var config = GetRegistryConfig();
38+
RegistryKey key = null;
39+
40+
try
41+
{
42+
foreach (var line in config.Split(new[] { Environment.NewLine }, StringSplitOptions.None))
43+
{
44+
if (line.Length == 0 || line[0] == ';')
45+
{
46+
continue; // skip empty lines and comments
47+
}
48+
49+
if (line[0] == '[')
50+
{
51+
key?.Dispose();
52+
key = OpenOrCreateKey(line);
53+
}
54+
else if (key is not null && (line[0] == '@' || line[0] == '"'))
55+
{
56+
SetValue(key, line);
57+
}
58+
}
59+
}
60+
catch (Exception exc)
61+
{
62+
logger.WriteLine("RegistryAction.Install failed:");
63+
logger.WriteLine(exc);
64+
return FAILURE;
65+
}
66+
finally
67+
{
68+
key?.Dispose();
69+
}
70+
71+
return SUCCESS;
72+
}
73+
74+
75+
private string GetRegistryConfig()
76+
{
77+
var env = architecture == Architecture.X86 ? "ProgramFiles(x86)" : "ProgramFiles";
78+
79+
var config = Properties.Resource.Registry
80+
.Replace("{OneMoreID}", RegistryHelper.OneMoreID)
81+
.Replace("{ProgramFiles}", Environment.GetEnvironmentVariable(env))
82+
.Replace("{Version}", AssemblyInfo.Version);
83+
84+
return config;
85+
}
86+
87+
88+
private RegistryKey OpenOrCreateKey(string line)
89+
{
90+
var raw = line.Trim('[', ']');
91+
92+
// extract hive name, ending up with something like "HKEY_CLASSES_ROOT"
93+
var hiveName = raw.Substring(0, raw.IndexOf('\\'));
94+
95+
// we only care about these two!
96+
var hive = hiveName.EndsWith("ROOT") ? Registry.ClassesRoot : Registry.CurrentUser;
97+
98+
// extract key path, ending up with something like "Software\OneMore"
99+
var keyName = raw.Substring(raw.IndexOf('\\') + 1);
100+
101+
var key = hive.OpenSubKey(keyName, RegistryKeyPermissionCheck.ReadWriteSubTree);
102+
if (key is null)
103+
{
104+
logger.WriteLine($"creating key: {hive.Name}\\{keyName}");
105+
key = hive.CreateSubKey(keyName, RegistryKeyPermissionCheck.ReadWriteSubTree);
106+
}
107+
else
108+
{
109+
logger.WriteLine($"opened key: {hive.Name}\\{keyName}");
110+
}
111+
112+
return key;
113+
}
114+
115+
116+
private void SetValue(RegistryKey key, string line)
117+
{
118+
var matches = Regex.Match(line, @"^(?<name>@|""\w+"")=(?<type>[^""]\w+:)?(?<value>.+)$");
119+
if (matches.Success)
120+
{
121+
var name = matches.Groups["name"].Value.Trim('"');
122+
var type = matches.Groups["type"].Value.TrimEnd(':');
123+
var value = matches.Groups["value"].Value.Trim('"');
124+
125+
if (name == "@")
126+
{
127+
name = string.Empty; // use empty string for default value
128+
}
129+
130+
if (type.Length == 0)
131+
{
132+
type = "string"; // default type is string
133+
}
134+
135+
var v = key.GetValue(name);
136+
if (v is not null && v.ToString() == value)
137+
{
138+
logger.WriteLine($"value already set: {name} = {value} ({type})");
139+
return; // no change needed
140+
}
141+
142+
logger.WriteLine($"setting value: {name} = {value} ({type})");
143+
144+
if (type == "dword")
145+
{
146+
var dword = int.Parse(value, System.Globalization.NumberStyles.HexNumber);
147+
key.SetValue(name, dword, RegistryValueKind.DWord);
148+
}
149+
else
150+
{
151+
key.SetValue(name, value, RegistryValueKind.String);
152+
}
153+
}
154+
}
155+
156+
//========================================================================================
157+
158+
public override int Uninstall()
159+
{
160+
logger.WriteLine();
161+
logger.WriteLine($"RegistryAction.Uninstall --- {architecture}");
162+
163+
var config = GetRegistryConfig();
164+
165+
string marker = null;
166+
167+
try
168+
{
169+
foreach (var line in config.Split(new[] { Environment.NewLine }, StringSplitOptions.None))
170+
{
171+
if (line.Length > 0 && line[0] == '[')
172+
{
173+
var raw = line.Trim('[', ']');
174+
if (marker is null || !raw.StartsWith(marker))
175+
{
176+
var hiveName = raw.Substring(0, raw.IndexOf('\\'));
177+
var hive = hiveName.EndsWith("ROOT") ? Registry.ClassesRoot : Registry.CurrentUser;
178+
var keyName = raw.Substring(raw.IndexOf('\\') + 1);
179+
180+
logger.WriteLine($"deleting tree: {raw}");
181+
hive.DeleteSubKeyTree(keyName, false);
182+
183+
// remember for next pass; only need to delete root of subtree
184+
marker = raw;
185+
}
186+
}
187+
}
188+
}
189+
catch (Exception exc)
190+
{
191+
logger.WriteLine("RegistryAction.Uninstall failed:");
192+
logger.WriteLine(exc);
193+
return FAILURE;
194+
}
195+
196+
return SUCCESS;
197+
}
198+
}
199+
}

OneMoreSetupActions/Actions/RegistryWowAction.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,34 @@ namespace OneMoreSetupActions
66
{
77
using Microsoft.Win32;
88
using System;
9+
using System.Runtime.InteropServices;
910

1011

1112
/// <summary>
1213
/// Clones the OneMore CLSID branch to support both 32bit and 64bit installs of OneNote
1314
/// </summary>
1415
internal class RegistryWowAction : CustomAction
1516
{
17+
private readonly Architecture architecture;
1618

17-
public RegistryWowAction(Logger logger, Stepper stepper)
19+
20+
public RegistryWowAction(Logger logger, Stepper stepper, Architecture onArchitecture)
1821
: base(logger, stepper)
1922
{
23+
architecture = onArchitecture;
2024
}
2125

2226

2327
//========================================================================================
2428

2529
/// <summary>
26-
/// Note this is invoked as its own CustomAction, not as part of Program
30+
/// Note this is invoked as a single call, not as part of Program:Install
2731
/// </summary>
2832
/// <returns></returns>
2933
public override int Install()
3034
{
3135
logger.WriteLine();
32-
logger.WriteLine($"RegistryWowAction.Install --- x64:{Environment.Is64BitProcess}");
36+
logger.WriteLine($"RegistryWowAction.Install --- OS x64:{Environment.Is64BitProcess}, OneNote:{architecture}");
3337

3438
if (CloningRequired())
3539
{
@@ -62,7 +66,7 @@ private int RegisterWow()
6266
{
6367
logger.WriteLine($"step {stepper.Step()}: cloning CLSID");
6468
using (var source = Registry.ClassesRoot.OpenSubKey(
65-
$@"CLSID\{RegistryHelper.OneNoteID}",
69+
$@"CLSID\{RegistryHelper.OneMoreID}",
6670
RegistryKeyPermissionCheck.ReadSubTree, RegistryHelper.ReadRights))
6771
{
6872
if (source != null)
@@ -103,8 +107,8 @@ private int UnregisterWow()
103107
{
104108
if (key != null)
105109
{
106-
key.DeleteSubKeyTree(RegistryHelper.OneNoteID, false);
107-
key.DeleteSubKey(RegistryHelper.OneNoteID, false);
110+
key.DeleteSubKeyTree(RegistryHelper.OneMoreID, false);
111+
key.DeleteSubKey(RegistryHelper.OneMoreID, false);
108112
}
109113
else
110114
{

OneMoreSetupActions/Actions/ShutdownOneNoteAction.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ private int StopHost()
7070

7171
var sql =
7272
"SELECT ProcessID, CommandLine FROM Win32_Process " +
73-
$"WHERE CommandLine LIKE '%{RegistryHelper.OneNoteID}%'";
73+
$"WHERE CommandLine LIKE '%{RegistryHelper.OneMoreID}%'";
7474

7575
try
7676
{

OneMoreSetupActions/Logger.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,19 @@ internal class Logger : IDisposable
1717

1818
public Logger(string name)
1919
{
20-
LogPath = Path.Combine(Path.GetTempPath(), $"{name}.log");
20+
// probably running under System account and TEMP path would be C:\Windows\SystemTemp
21+
// but most users don't have access to read that. So, first try the user's TEMP path
22+
// and, if it is SystemTemp, instead use C:\Windows\TEMP
23+
24+
var temp = Path.GetTempPath();
25+
if (temp.Contains("SystemTemp"))
26+
{
27+
temp = Path.Combine(Environment.GetEnvironmentVariable("SystemRoot"), "TEMP");
28+
}
29+
30+
LogPath = Path.Combine(temp, $"{name}.log");
31+
Console.WriteLine($"Logging to: {LogPath}");
32+
2133
writer = new StreamWriter(LogPath, true);
2234
}
2335

0 commit comments

Comments
 (0)