-
Notifications
You must be signed in to change notification settings - Fork 0
/
Manager.cs
479 lines (391 loc) · 19.6 KB
/
Manager.cs
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
//
// @(#) Manager.cs
//
// Project: usis.Data.LocalDb
// System: Microsoft Visual Studio 2022
// Author: Udo Schäfer
//
// Copyright (c) 2018-2023 usis GmbH. All rights reserved.
using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
namespace usis.Data.LocalDb
{
// -------------
// Manager class
// -------------
/// <summary>
/// Provides methods to manage LocalDB instances.
/// </summary>
/// <seealso cref="IDisposable"/>
public sealed class Manager : IDisposable
{
#region fields
private readonly NativeLibraryHandle library;
private LocalDBFormatMessage localDBFormatMessage;
private LocalDBGetVersions localDBGetVersions;
private LocalDBGetVersionInfo localDBGetVersionInfo;
private LocalDBGetInstances localDBGetInstances;
private LocalDBGetInstanceInfo localDBGetInstanceInfo;
private LocalDBCreateInstance localDBCreateInstance;
private LocalDBDeleteInstance localDBDeleteInstance;
private LocalDBStartInstance localDBStartInstance;
private LocalDBStopInstance localDBStopInstance;
private LocalDBShareInstance localDBShareInstance;
private LocalDBUnshareInstance localDBUnshareInstance;
private LocalDBStartTracing localDBStartTracing;
private LocalDBStopTracing localDBStopTracing;
#endregion
#region delegates
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate uint LocalDBFormatMessage(uint hr, int flags, int languageId, [MarshalAs(UnmanagedType.LPWStr)][Out] StringBuilder message, ref int size);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate uint LocalDBGetVersions(IntPtr versionNames, ref int numberOfVersions);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate uint LocalDBGetVersionInfo([MarshalAs(UnmanagedType.LPWStr)] string versionName, [MarshalAs(UnmanagedType.Struct)] out LocalDBVersionInfo versionInfo, int versionInfoSize);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate uint LocalDBGetInstances(IntPtr instanceNames, ref int numberOfInstances);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate uint LocalDBGetInstanceInfo([MarshalAs(UnmanagedType.LPWStr)] string instanceName, [MarshalAs(UnmanagedType.Struct)] out LocalDBInstanceInfo instanceInfo, int instanceInfoSize);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate uint LocalDBCreateInstance([MarshalAs(UnmanagedType.LPWStr)] string version, [MarshalAs(UnmanagedType.LPWStr)] string instanceName, uint flags);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate uint LocalDBDeleteInstance([MarshalAs(UnmanagedType.LPWStr)] string instanceName, uint flags);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate uint LocalDBStartInstance([MarshalAs(UnmanagedType.LPWStr)] string instanceName, uint flags, [MarshalAs(UnmanagedType.LPWStr)][Out] StringBuilder sqlConnection, ref int sqlConnectionSize);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate uint LocalDBStopInstance([MarshalAs(UnmanagedType.LPWStr)] string instanceName, uint flags, uint timeout);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate uint LocalDBShareInstance(IntPtr ownerSID, [MarshalAs(UnmanagedType.LPWStr)] string instancePrivateName, [MarshalAs(UnmanagedType.LPWStr)] string instanceSharedName, uint flags);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate uint LocalDBUnshareInstance([MarshalAs(UnmanagedType.LPWStr)] string instanceSharedName, uint flags);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate uint LocalDBStartTracing();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate uint LocalDBStopTracing();
#endregion
#region construction
// ------------
// construction
// ------------
/// <summary>
/// Initializes a new instance of the <see cref="Manager"/> class.
/// </summary>
/// <param name="path">The path of the LocalDB API library.</param>
private Manager(string path) => library = new NativeLibraryHandle(path);
#endregion
#region IDisposable implementation
// --------------
// Dispose method
// --------------
/// <summary>
/// Performs application-defined tasks associated with freeing,
/// releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose() => library.Dispose();
#endregion
#region methods
// ------------------
// IsInstalled method
// ------------------
/// <summary>
/// Determines whether SQL Server Express LocalDB is installed on the
/// computer.
/// </summary>
/// <returns><c>true</c> if SQL Server Express LocalDB is installed on
/// the computer; otherwise, <c>false</c>.</returns>
public static bool IsInstalled() => InstalledVersions.FromRegistry().Count > 0;
// -------------
// Create method
// -------------
/// <summary>
/// Creates a new instance of the <see cref="Manager"/> class.
/// </summary>
/// <returns>A newly created instance of the <see cref="Manager"/>
/// class.</returns>
public static Manager Create()
{
var dictionary = InstalledVersions.FromRegistry();
return dictionary.Count == 0 ? throw new LocalDbException(Strings.NotInstalled) : new Manager(dictionary.LastOrDefault().Value);
}
// ------------------
// GetVersions method
// ------------------
/// <summary>
/// Returns all SQL Server Express LocalDB versions available on the
/// computer.
/// </summary>
/// <returns>An array that contains the names of the LocalDB versions
/// that are available on the user’s workstation.</returns>
public string[] GetVersions()
{
var function = library.GetFunction(nameof(LocalDBGetVersions), ref localDBGetVersions);
var count = 0;
if (!ValidateHResult(function(IntPtr.Zero, ref count), Constants.LOCALDB_ERROR_INSUFFICIENT_BUFFER))
{
var size = (Constants.MAX_LOCALDB_VERSION_LENGTH + 1) * sizeof(char);
var pVersions = Marshal.AllocHGlobal(size * count);
try
{
if (ValidateHResult(function(pVersions, ref count)))
{
return pVersions.Enumerate(count, size, Marshal.PtrToStringAuto).ToArray();
}
}
finally
{
Marshal.FreeHGlobal(pVersions);
}
}
return [];
}
// ---------------------
// GetVersionInfo method
// ---------------------
/// <summary>
/// Gets information for the specified SQL Server Express LocalDB
/// version, such as whether it exists and the full LocalDB version
/// number (including build and release numbers).
/// </summary>
/// <param name="version">The LocalDB version name.</param>
/// <returns>The information about the specified LocalDB version.
/// </returns>
public VersionInfo GetVersionInfo(string version)
{
var info = new LocalDBVersionInfo();
var hr = library.GetFunction(nameof(LocalDBGetVersionInfo), ref localDBGetVersionInfo)(version, out info, Marshal.SizeOf(typeof(LocalDBVersionInfo)));
return ValidateHResult(hr) ? new VersionInfo(info) : null;
}
// -------------------
// GetInstances method
// -------------------
/// <summary>
/// Gets the names of both named and default LocalDB instances on the
/// user’s workstation.
/// </summary>
/// <returns>An array that contains the names of both named and default LocalDB instances on the user’s workstation.</returns>
/// <example>
///using System;
///
///namespace usis.Data.LocalDb.Samples
///{
/// public static class Operations
/// {
/// public static void ListInstances()
/// {
/// using (var manager = Manager.Create())
/// {
/// foreach (var instance in manager.GetInstances())
/// {
/// Console.WriteLine(instance);
/// }
/// }
/// }
/// }
///}
/// </example>
public string[] GetInstances(double timeout = 3000)
{
var function = library.GetFunction(nameof(LocalDBGetInstances), ref localDBGetInstances);
var count = 0;
if (!ValidateHResult(function(IntPtr.Zero, ref count), Constants.LOCALDB_ERROR_INSUFFICIENT_BUFFER))
{
var stopWatch = Stopwatch.StartNew();
do
{
var size = (Constants.MAX_LOCALDB_INSTANCE_NAME_LENGTH + 1) * sizeof(char);
var pInstances = Marshal.AllocHGlobal(size * count);
try
{
if (ValidateHResult(function(pInstances, ref count), Constants.LOCALDB_ERROR_INSUFFICIENT_BUFFER))
{
return pInstances.Enumerate(count, size, Marshal.PtrToStringAuto).ToArray();
}
}
finally
{
Marshal.FreeHGlobal(pInstances);
}
}
while (stopWatch.Elapsed.TotalMilliseconds < timeout || double.IsInfinity(timeout));
throw new TimeoutException();
}
return [];
}
// ----------------------
// GetInstanceInfo method
// ----------------------
/// <summary>
/// Gets information for the specified SQL Server Express LocalDB
/// instance, such as whether it exists, the LocalDB version it uses,
/// whether it is running, and so on.
/// </summary>
/// <param name="instanceName">Name of the instance.</param>
/// <returns>The information about the specified LocalDB instance.
/// </returns>
public InstanceInfo GetInstanceInfo(string instanceName)
{
var info = new LocalDBInstanceInfo();
var hr = library.GetFunction(nameof(LocalDBGetInstanceInfo), ref localDBGetInstanceInfo)(instanceName, out info, Marshal.SizeOf(typeof(LocalDBInstanceInfo)));
return ValidateHResult(hr) ? new InstanceInfo(info) : null;
}
// ---------------------
// CreateInstance method
// ---------------------
/// <summary>
/// Creates a new SQL Server Express LocalDB instance.
/// </summary>
/// <param name="version">The LocalDB version, for example <c>11.0</c>
/// or <c>11.0.1094.2</c>.</param>
/// <param name="instanceName">The name for the LocalDB instance to
/// create.</param>
public void CreateInstance(string version, string instanceName) => _ = ValidateHResult(library.GetFunction(nameof(LocalDBCreateInstance), ref localDBCreateInstance)(version, instanceName, 0));
// ---------------------
// DeleteInstance method
// ---------------------
/// <summary>
/// Removes the specified SQL Server Express LocalDB instance.
/// </summary>
/// <param name="instanceName">The name of the LocalDB instance to
/// remove.</param>
public void DeleteInstance(string instanceName) => ValidateHResult(library.GetFunction(nameof(LocalDBDeleteInstance), ref localDBDeleteInstance)(instanceName, 0));
// --------------------
// StartInstance method
// --------------------
/// <summary>
/// Starts the specified SQL Server Express LocalDB instance.
/// </summary>
/// <param name="instanceName">The name of the LocalDB instance to
/// start.</param>
/// <returns>The name of the TDS named pipe to connect to the instance.
/// </returns>
public string StartInstance(string instanceName)
{
var size = Constants.LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE + 1;
var buffer = new StringBuilder(size);
var hr = library.GetFunction(nameof(LocalDBStartInstance), ref localDBStartInstance)(instanceName, 0, buffer, ref size);
return ValidateHResult(hr) ? buffer.ToString() : null;
}
// -------------------
// StopInstance method
// -------------------
/// <summary>
/// Stops the specified SQL Server Express LocalDB instance from
/// running.
/// </summary>
/// <param name="instanceName">The name of the LocalDB instance to stop.
/// </param>
/// <param name="options">One or a combination of the option values
/// specifying the way to stop the instance.</param>
/// <param name="timeout">The time to wait for this operation to
/// complete. If this value is <c>0</c>, this function will return
/// immediately without waiting for the LocalDB instance to stop.
/// </param>
public void StopInstance(string instanceName, StopInstanceOptions options, TimeSpan timeout)
{
// if timeout is 0, the function returns immediately with an error code
var t = Convert.ToUInt32(timeout.TotalSeconds);
var hr = library.GetFunction(nameof(LocalDBStopInstance), ref localDBStopInstance)(instanceName, (uint)options, t);
_ = ValidateHResult(hr, t == 0 ? Constants.LOCALDB_ERROR_WAIT_TIMEOUT : 0);
}
// --------------------
// ShareInstance method
// --------------------
/// <summary>
/// Shares the specified SQL Server Express LocalDB instance with other
/// users of the computer, using the specified shared name.
/// </summary>
/// <param name="owner">The SID of the instance owner.</param>
/// <param name="instancePrivateName">The private name for the LocalDB
/// instance to share.</param>
/// <param name="instanceSharedName">The shared name for the LocalDB
/// instance to share.</param>
public void ShareInstance(string owner, string instancePrivateName, string instanceSharedName)
{
var sid = new SecurityIdentifier(owner);
var bytes = new byte[sid.BinaryLength];
sid.GetBinaryForm(bytes, 0);
var ownerSID = Marshal.AllocHGlobal(bytes.Length);
try
{
Marshal.Copy(bytes, 0, ownerSID, bytes.Length);
var hr = library.GetFunction(nameof(LocalDBShareInstance), ref localDBShareInstance)(ownerSID, instancePrivateName, instanceSharedName, 0);
_ = ValidateHResult(hr);
}
finally
{
Marshal.FreeHGlobal(ownerSID);
}
}
// ----------------------
// UnshareInstance method
// ----------------------
/// <summary>
/// Stops the sharing of the specified SQL Server Express LocalDB
/// instance.
/// </summary>
/// <param name="instanceName">The shared name for the LocalDB instance
/// to unshare.</param>
public void UnshareInstance(string instanceName) => ValidateHResult(library.GetFunction(nameof(LocalDBUnshareInstance), ref localDBUnshareInstance)(instanceName, 0));
// -------------------
// StartTracing method
// -------------------
/// <summary>
/// Enables tracing of API calls for all the SQL Server Express LocalDB
/// instances owned by the current Windows user.
/// </summary>
public void StartTracing() => ValidateHResult(library.GetFunction(nameof(LocalDBStartTracing), ref localDBStartTracing)());
// ------------------
// StopTracing method
// ------------------
/// <summary>
/// Disables tracing of API calls for all the SQL Server Express LocalDB
/// instances owned by the current Windows user.
/// </summary>
public void StopTracing() => ValidateHResult(library.GetFunction(nameof(LocalDBStopTracing), ref localDBStopTracing)());
#region private methods
// --------------------
// FormatMessage method
// --------------------
private string FormatMessage(uint hr, int flags)
{
if (hr == Constants.LOCALDB_ERROR_NOT_INSTALLED) return Strings.NotInstalled;
var function = library.GetFunction(nameof(LocalDBFormatMessage), ref localDBFormatMessage);
var size = 0;
var builder = new StringBuilder();
var result = function(hr, flags, 0, builder, ref size);
if (result == Constants.LOCALDB_ERROR_INSUFFICIENT_BUFFER)
{
builder.Capacity = size;
result = function(hr, flags, 0, builder, ref size);
if (result == 0) return builder.ToString();
}
var message = MessageForError(result, hr) ?? string.Format(CultureInfo.CurrentCulture, Strings.ErrorCode, result);
return string.Format(CultureInfo.CurrentCulture, Strings.FailedToRetrieveMessage, hr, message);
// ----------------------
// MessageForError method
// ----------------------
static string MessageForError(uint error, uint hr) => error switch
{
Constants.LOCALDB_ERROR_NOT_INSTALLED => Strings.NotInstalled,
Constants.LOCALDB_ERROR_INVALID_PARAMETER => Strings.InvalidParameter,
Constants.LOCALDB_ERROR_UNKNOWN_ERROR_CODE => string.Format(CultureInfo.CurrentCulture, Strings.UnknownErrorCode, hr),
Constants.LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID => Strings.UnknownLanguageId,
Constants.LOCALDB_ERROR_INSUFFICIENT_BUFFER => Strings.InsufficientBuffer,
Constants.LOCALDB_ERROR_INTERNAL_ERROR => Strings.InternalError,
_ => string.Empty,
};
}
// ----------------------
// ValidateHResult method
// ----------------------
private bool ValidateHResult(uint hr, params uint[] values) => hr.ValidateHResult(error => new LocalDbException(error, FormatMessage(hr, 0)), values);
#endregion
#endregion
}
}
// eof "Manager.cs"