-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathApplicationUriSchema.cs
More file actions
166 lines (140 loc) · 6.96 KB
/
ApplicationUriSchema.cs
File metadata and controls
166 lines (140 loc) · 6.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
using System;
using System.Collections.Generic;
using System.Deployment.Application;
using System.Diagnostics;
using System.Reflection;
using System.Windows;
using Microsoft.Win32;
using NLog;
namespace ClickOnceCustomUriScheme.ApplicationUri
{
/// <summary>
///
/// Registering an Application to a URI Scheme: https://msdn.microsoft.com/en-us/library/aa767914(v=vs.85).aspx
///
/// Keys are registered in User's space (HKCU)
/// </summary>
public static class ApplicationUriSchema
{
private const string SchemaDefinition = "theapp";
private const string CustomUriSwitch = "-clickonce";
private static readonly string UriRegistrationKey = $"Software\\Classes\\{SchemaDefinition}";
private static readonly string CommandRegistrationKey = $"{UriRegistrationKey}\\shell\\open\\command";
private static string PathToCurrentExecutable => Assembly.GetExecutingAssembly().Location;
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
// Possible improvement:
// If there are other consumers of startup arguments, then take out consumed arguments and return what's left
public static void Handle(List<string> startupArgs)
{
if (startupArgs.RemoveAll(x => x.ToLowerInvariant() == CustomUriSwitch) > 0)
HandleStartupFromCustomUriScheme(startupArgs);
CheckCustomUriRegistration();
}
private static void HandleStartupFromCustomUriScheme(IReadOnlyList<string> startupArgs)
{
if (ApplicationDeployment.IsNetworkDeployed)
{
Log.Error("When Application is deployed and run as ClickOnce the StartupArgs should not contain -clickonce parameter.");
Application.Current.Shutdown();
return;
}
Log.Debug("Application is launched with -clickonce key. Attempt to run as ClickOnce application");
if (startupArgs.Count != 2)
{
Log.Error("StartupArgs count mismatch. " +
"Application running with -clickonce key expects 2 additional parameters: " +
"uri to clickonce deployment and the original URI that is executing. " +
"Application execution will continue as a normal application, non ClickOnce deployed");
Application.Current.Shutdown();
return;
}
var clickOnceDeploymentUri = startupArgs[0];
var schemeUri = new Uri(startupArgs[1]);
// We only support URI with a single hop: either host specified, or no host and a path
// Valid examples: theapp://ui-module; theapp:ui-module
// Invalid examples: theapp:/ui-module/sub, theapp:ui-module/sub
// if '//' is specified, then host is not empty. without '//' host is null
var applicationPath = $"{schemeUri.Host}/{schemeUri.AbsolutePath}".Trim('/');
if (applicationPath.Contains("/"))
{
Log.Error("The path in provided URI contains multiple segments. " +
"Only single-segment paths are allowed. Good examples: theapp://ui-module, theapp:ui-module. " +
$"Bad example: theapp://ui-module/segment. Provided uri value: {schemeUri}, extracted path: {applicationPath}");
Application.Current.Shutdown();
return;
}
var schemeUriQuery = schemeUri.Query.Trim('?', ';');
if (!string.IsNullOrEmpty(schemeUriQuery))
schemeUriQuery = $"&{schemeUriQuery}";
var clickOnceQuery = $"applicationPath={applicationPath}{schemeUriQuery}";
var clickOnceLaunchCommand = $"{clickOnceDeploymentUri}?{clickOnceQuery}";
Log.Debug("ClickOnce launch command: " + clickOnceLaunchCommand);
var processInfo = new ProcessStartInfo
{
FileName = "iexplore.exe",
Arguments = clickOnceLaunchCommand,
UseShellExecute = true
};
Log.Debug("Launching application in ClickOnce context, then shutting down");
new Process { StartInfo = processInfo }.Start();
Application.Current.Shutdown();
}
private static void CheckCustomUriRegistration()
{
if (!ApplicationDeployment.IsNetworkDeployed)
{
Log.Debug("Application is not deployed using ClickOnce. Uri Scheme will not be registered");
return;
}
if (!ApplicationDeployment.CurrentDeployment.IsFirstRun)
{
Log.Debug("This is not first run of the application, skipping registration of custom URI schema");
return;
}
WriteRequiredRegistryKeys();
Log.Debug("Application's Custom Uri schema configuration completed");
}
private static void WriteRequiredRegistryKeys()
{
using (var uriKey = Registry.CurrentUser.CreateSubKey(UriRegistrationKey))
{
if (uriKey == null)
{
Log.Debug($"Registration key cannot be created ({UriRegistrationKey})");
return;
}
uriKey.SetValue(null, "URL:Catalyst protocol", RegistryValueKind.String);
uriKey.SetValue("URL Protocol", "", RegistryValueKind.String);
}
var defaultIconKey = Registry.CurrentUser.CreateSubKey("DefaultIcon");
if (defaultIconKey == null)
{
Log.Error("default icon key is null");
return;
}
defaultIconKey.SetValue(null, $"{PathToCurrentExecutable},1");
using (var commandKey = Registry.CurrentUser.CreateSubKey(CommandRegistrationKey))
{
if (commandKey == null)
{
Log.Error($"Key {CommandRegistrationKey} was not created");
return;
}
var launchCommand = GetApplicationLaunchCommand();
Log.Debug($"Command to launch application: {launchCommand}");
commandKey.SetValue(null, launchCommand, RegistryValueKind.String);
}
}
private static string GetApplicationLaunchCommand()
{
var uri = ApplicationDeployment.CurrentDeployment.ActivationUri?.AbsoluteUri;
if (uri == null)
throw new InvalidOperationException("ApplicationDeployment.CurrentDeployment.ActivationUri is null");
// In case if application was launched by URI schema handler and was also updated, then
// uri might contain query parameters. We want only the absolute uri.
if (uri.Contains("?"))
uri = uri.Substring(0, uri.IndexOf('?'));
return $"\"{PathToCurrentExecutable}\" {CustomUriSwitch} \"{uri}\" \"%1\"";
}
}
}