Cleaned BITS file syntax and added support for v10.2 and 10.3 methods. Fixed #257 problem with BackgroundCopyJob constructor throwing error on systems prior to Win 10 and deficiencies with BackgroundCopyManager.Version property.

pull/267/head
dahall 2021-12-10 17:36:05 -07:00
parent 012034f883
commit c0d5fe8ab0
7 changed files with 144 additions and 79 deletions

View File

@ -9,10 +9,10 @@ namespace Vanara.IO
/// <summary>Exceptions specific to BITS</summary>
public class BackgroundCopyException : Exception
{
private HRESULT code;
private BG_ERROR_CONTEXT ctx;
private string ctxDesc, errDesc, protocol;
private IBackgroundCopyFile iVal;
private readonly HRESULT code;
private readonly BG_ERROR_CONTEXT ctx;
private readonly string ctxDesc, errDesc, protocol;
private readonly IBackgroundCopyFile iVal;
internal BackgroundCopyException(IBackgroundCopyError err)
{
@ -31,7 +31,7 @@ namespace Vanara.IO
{
code = cex.ErrorCode;
errDesc = BackgroundCopyManager.GetErrorMessage(code);
if (errDesc == null)
if (errDesc is null)
code.ThrowIfFailed();
}
@ -48,7 +48,7 @@ namespace Vanara.IO
/// <summary>If error was related to a file, returns information about the file and its progress. Otherwise, returns NULL.</summary>
public BackgroundCopyFileInfo File
{
get { if (iVal == null) return null; return new BackgroundCopyFileInfo(iVal); }
get { if (iVal is null) return null; return new BackgroundCopyFileInfo(iVal); }
}
/// <summary>The error text associated with the error.</summary>

View File

@ -124,7 +124,7 @@ namespace Vanara.IO
/// <param name="length">Number of bytes in the range.</param>
public void Add(string remoteFilePath, string localFilePath, long initialOffset, long length = -1)
{
IBackgroundCopyJob3 ijob3 = null;
IBackgroundCopyJob3 ijob3;
try { ijob3 = (IBackgroundCopyJob3)m_ijob; }
catch { throw new NotSupportedException(); }
var rng = new[] { new BG_FILE_RANGE { InitialOffset = (ulong)initialOffset, Length = length == -1 ? ulong.MaxValue : (ulong)length } };
@ -274,8 +274,8 @@ namespace Vanara.IO
private static bool TryGetNext(IEnumBackgroundCopyFiles e, out BackgroundCopyFileInfo i)
{
var ifi = e.Next(1)?.FirstOrDefault();
i = ifi != null ? new BackgroundCopyFileInfo(ifi) : null;
return i != null;
i = ifi is not null ? new BackgroundCopyFileInfo(ifi) : null;
return i is not null;
}
}
}

View File

@ -10,7 +10,7 @@ namespace Vanara.IO
{
internal BG_FILE_INFO fi;
private IBackgroundCopyFile iFile;
private readonly IBackgroundCopyFile iFile;
internal BackgroundCopyFileInfo(IBackgroundCopyFile ibgfile)
{
@ -58,10 +58,10 @@ namespace Vanara.IO
/// <summary>Retrieves the local name of the file.</summary>
public string LocalFilePath
{
get => iFile == null ? fi.LocalName : iFile.GetLocalName();
get => iFile is null ? fi.LocalName : iFile.GetLocalName();
set
{
if (iFile != null)
if (iFile is not null)
throw new InvalidOperationException("You cannot change the LocalFilePath property on CurrentFileSet results.");
fi.LocalName = value;
}
@ -86,7 +86,7 @@ namespace Vanara.IO
/// <summary>Retrieves the remote name of the file.</summary>
public string RemoteFilePath
{
get => iFile == null ? fi.RemoteName : iFile.GetRemoteName();
get => iFile is null ? fi.RemoteName : iFile.GetRemoteName();
set
{
fi.RemoteName = value;
@ -159,11 +159,7 @@ namespace Vanara.IO
/// <param name="offset">Specifies the new position to prioritize downloading missing data from.</param>
public void UpdateDownloadPosition(ulong offset) => IFile6.UpdateDownloadPosition(offset);
private T GetDerived<T>() where T : class
{
T ret = iFile as T;
return ret ?? throw new PlatformNotSupportedException();
}
private T GetDerived<T>() where T : class => iFile as T ?? throw new PlatformNotSupportedException();
}
/// <summary>Identifies a range of bytes to download from a file.</summary>
@ -183,6 +179,6 @@ namespace Vanara.IO
/// <summary>Performs an implicit conversion from <see cref="BG_FILE_RANGE"/> to <see cref="BackgroundCopyFileRange"/>.</summary>
/// <param name="p">The BG_FILE_RANGE instance.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator BackgroundCopyFileRange(BG_FILE_RANGE p) => new BackgroundCopyFileRange { fr = p };
public static implicit operator BackgroundCopyFileRange(BG_FILE_RANGE p) => new() { fr = p };
}
}

View File

@ -46,7 +46,7 @@ namespace Vanara.IO
/// <summary>Performs an implicit conversion from <see cref="BG_JOB_PROGRESS"/> to <see cref="BackgroundCopyJobProgress"/>.</summary>
/// <param name="p">The BG_JOB_PROGRESS instance.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator BackgroundCopyJobProgress(BG_JOB_PROGRESS p) => new BackgroundCopyJobProgress(p);
public static implicit operator BackgroundCopyJobProgress(BG_JOB_PROGRESS p) => new(p);
}
/// <summary>Provides progress information related to the reply portion of an upload-reply job.</summary>
@ -66,7 +66,7 @@ namespace Vanara.IO
/// <summary>Performs an implicit conversion from <see cref="BG_JOB_REPLY_PROGRESS"/> to <see cref="BackgroundCopyJobReplyProgress"/>.</summary>
/// <param name="p">The BG_JOB_REPLY_PROGRESS instance.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator BackgroundCopyJobReplyProgress(BG_JOB_REPLY_PROGRESS p) => new BackgroundCopyJobReplyProgress(p);
public static implicit operator BackgroundCopyJobReplyProgress(BG_JOB_REPLY_PROGRESS p) => new(p);
}
/// <summary>Used by <see cref="BackgroundCopyJob.FileRangesTransferred"/> events.</summary>
@ -118,6 +118,8 @@ namespace Vanara.IO
internal static readonly TimeSpan DEFAULT_RETRY_DELAY = TimeSpan.FromSeconds(600); //10 minutes (600 seconds)
internal static readonly TimeSpan DEFAULT_RETRY_PERIOD = TimeSpan.FromSeconds(1209600); //20160 minutes (1209600 seconds)
internal static readonly TimeSpan DEFAULT_TIMEOUT = TimeSpan.FromSeconds(7776000); // 7776000 seconds
internal static readonly Version CopyCallback2 = new(3, 0);
internal static readonly Version CopyCallback3 = new(10, 1);
private IBackgroundCopyJob m_ijob;
private Notifier m_notifier;
@ -127,7 +129,13 @@ namespace Vanara.IO
m_ijob = ijob ?? throw new ArgumentNullException(nameof(ijob));
m_notifier = new Notifier(this);
m_ijob.SetNotifyInterface(m_notifier);
NotifyFlags = BG_NOTIFY.BG_NOTIFY_FILE_RANGES_TRANSFERRED | BG_NOTIFY.BG_NOTIFY_FILE_TRANSFERRED | BG_NOTIFY.BG_NOTIFY_JOB_ERROR | BG_NOTIFY.BG_NOTIFY_JOB_MODIFICATION | BG_NOTIFY.BG_NOTIFY_JOB_TRANSFERRED;
var bitsVer = BackgroundCopyManager.Version;
NotifyFlags = bitsVer switch
{
var v when v >= CopyCallback3 => BG_NOTIFY.BG_NOTIFY_FILE_RANGES_TRANSFERRED | BG_NOTIFY.BG_NOTIFY_FILE_TRANSFERRED | BG_NOTIFY.BG_NOTIFY_JOB_ERROR | BG_NOTIFY.BG_NOTIFY_JOB_MODIFICATION | BG_NOTIFY.BG_NOTIFY_JOB_TRANSFERRED,
var v when v >= CopyCallback2 => BG_NOTIFY.BG_NOTIFY_FILE_TRANSFERRED | BG_NOTIFY.BG_NOTIFY_JOB_ERROR | BG_NOTIFY.BG_NOTIFY_JOB_MODIFICATION | BG_NOTIFY.BG_NOTIFY_JOB_TRANSFERRED,
_ => BG_NOTIFY.BG_NOTIFY_JOB_ERROR | BG_NOTIFY.BG_NOTIFY_JOB_MODIFICATION | BG_NOTIFY.BG_NOTIFY_JOB_TRANSFERRED,
};
Files = new BackgroundCopyFileCollection(m_ijob);
Credentials = new BackgroundCopyJobCredentials(IJob2);
}
@ -164,25 +172,13 @@ namespace Vanara.IO
{
IHttpOp.GetClientCertificate(out var loc, out var mstore, out var blob, out var subj);
if (blob.IsInvalid) return null;
var store = mstore;
switch (store)
var store = mstore switch
{
case "MY":
store = "My";
break;
case "ROOT":
store = "Root";
break;
case "SPC":
store = "TrustedPublisher";
break;
case "CA":
default:
break;
}
"MY" => "My",
"ROOT" => "Root",
"SPC" => "TrustedPublisher",
_ => mstore,
};
var xstore = new X509Store(store, (StoreLocation)(loc + 1));
xstore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
return xstore.Certificates.Find(X509FindType.FindBySubjectName, subj, false).OfType<X509Certificate2>().FirstOrDefault() ??
@ -200,7 +196,7 @@ namespace Vanara.IO
{
var hdr = new System.Net.WebHeaderCollection();
var str = RunAction(() => IHttpOp.GetCustomHeaders().ToString(), null);
if (str != null)
if (str is not null)
{
foreach (var s in str.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries))
hdr.Add(s);
@ -288,6 +284,36 @@ namespace Vanara.IO
set => SetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_HIGH_PERFORMANCE, value);
}
/// <summary>Gets or sets the default HTTP method used for a BITS transfer.</summary>
/// <value>The HTTP method name.</value>
/// <remarks>
/// <para>
/// BITS allows you, as the developer, to choose an HTTP method other than the default method. This increases BITS' ability to
/// interact with servers that don't adhere to the normal BITS requirements for HTTP servers. Bear the following in mind when you
/// choose a different HTTP method from the default one.
/// </para>
/// <list type="bullet">
/// <item>BITS automatically changes the job priority to BG_JOB_PRIORITY_FOREGROUND, and prevents that priority from being changed.</item>
/// <item>
/// An error that would ordinarily be resumable (such as loss of connectivity) transitions the job to an ERROR state. You, as the
/// developer, can restart the job by calling IBackgroundCopyJob::Resume, and the job will be restarted from the beginning. See Life
/// Cycle of a BITS Job for more information on BITS job states.
/// </item>
/// <item>BITS doesnt allow DYNAMIC_CONTENT nor ON_DEMAND_MODE jobs with <c>SetHttpMethod</c>.</item>
/// </list>
/// <para>
/// <c>SetHttpMethod</c> does nothing if the method name that you pass matches the default HTTP method for the transfer type. For
/// example, if you set a download job method to "GET" (the default), then the job priority won't be changed. The HTTP method must
/// be set before the first call to IBackgroundCopyJob::Resume that starts the job.
/// </para>
/// </remarks>
[DefaultValue(null)]
public string HttpMethod
{
get => RunAction(() => IHttpOp2.GetHttpMethod());
set => RunAction(() => IHttpOp2.SetHttpMethod(value));
}
/// <summary>Gets the job identifier.</summary>
public Guid ID => RunAction(() => m_ijob.GetId(), Guid.Empty);
@ -303,7 +329,7 @@ namespace Vanara.IO
if (state != BackgroundCopyJobState.Error && state != BackgroundCopyJobState.TransientError)
return null;
var err = RunAction(() => m_ijob.GetError());
return err == null ? null : new BackgroundCopyException(err);
return err is null ? null : new BackgroundCopyException(err);
}
}
@ -402,7 +428,7 @@ namespace Vanara.IO
}
if (string.IsNullOrEmpty(a))
{
if (p == null)
if (p is null)
return string.Empty;
else
return p;
@ -501,13 +527,13 @@ namespace Vanara.IO
});
set => RunAction(() =>
{
if (value == null)
if (value is null)
m_ijob.SetProxySettings(BG_JOB_PROXY_USAGE.BG_JOB_PROXY_USAGE_PRECONFIG, null, null);
else if (string.IsNullOrEmpty(value.Address.AbsoluteUri))
m_ijob.SetProxySettings(BG_JOB_PROXY_USAGE.BG_JOB_PROXY_USAGE_NO_PROXY, null, null);
else
m_ijob.SetProxySettings(BG_JOB_PROXY_USAGE.BG_JOB_PROXY_USAGE_OVERRIDE, value.Address.AbsoluteUri, string.Join(" ", value.BypassList));
if (value.Credentials != null)
if (value.Credentials is not null)
throw new ArgumentException("The set Proxy property does not support proxy credentials. Please use the SetCredentials method.");
});
}
@ -581,6 +607,10 @@ namespace Vanara.IO
private IBackgroundCopyJobHttpOptions IHttpOp => GetDerived<IBackgroundCopyJobHttpOptions>();
private IBackgroundCopyJobHttpOptions2 IHttpOp2 => GetDerived<IBackgroundCopyJobHttpOptions2>();
private IBackgroundCopyJobHttpOptions3 IHttpOp3 => GetDerived<IBackgroundCopyJobHttpOptions3>();
private IBackgroundCopyJob2 IJob2 => GetDerived<IBackgroundCopyJob2>();
private IBackgroundCopyJob3 IJob3 => GetDerived<IBackgroundCopyJob3>();
@ -627,6 +657,17 @@ namespace Vanara.IO
/// <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.</returns>
public override int GetHashCode() => ID.GetHashCode();
/// <summary>
/// Sets the HTTP custom headers for this job to be write-only. Write-only headers cannot be read by BITS methods such as the <see
/// cref="CustomHeaders"/> property.
/// </summary>
/// <remarks>
/// Use this API when your BITS custom headers must include security information (such as an API token) that you don't want to be
/// readable by other programs running on the same computer. The BITS process, of course, can still read these headers, and send
/// them over the HTTP connection. Once the headers are set to write-only, that cannot be unset.
/// </remarks>
public void MakeCustomHeadersWriteOnly() => RunAction(() => IHttpOp3.MakeCustomHeadersWriteOnly());
/// <summary>
/// Use the ReplaceRemotePrefix method to replace the beginning text of all remote names in the download job with the given string.
/// </summary>
@ -686,6 +727,46 @@ namespace Vanara.IO
RunAction(() => IJob2.SetCredentials(ref ac));
}
/// <summary>
/// Server certificates are sent when an HTTPS connection is opened. Use this method to set a callback to be called to validate
/// those server certificates.
/// </summary>
/// <param name="callback">
/// An object that implements <see cref="IBackgroundCopyServerCertificateValidationCallback"/>. To remove the current callback
/// interface pointer, set this parameter to <see langword="null"/>.
/// </param>
/// <remarks>
/// <para>Use this method when you want to perform your own checks on the server certificate.</para>
/// <para>Call this method only if you implement the <see cref="IBackgroundCopyServerCertificateValidationCallback"/> interface.</para>
/// <para>
/// The validation interface becomes invalid when your application terminates; BITS does not maintain a record of the validation
/// interface. As a result, your application's initialization process should call <c>SetServerCertificateValidationInterface</c> on
/// those existing jobs for which you want to receive certificate validation requests.
/// </para>
/// <para>
/// If more than one application calls <c>SetServerCertificateValidationInterface</c> to set the notification interface for the job,
/// the last application to call it is the one that will receive notifications. The other applications will not receive notifications.
/// </para>
/// <para>
/// If any certificate errors are found during the OS validation of the certificate, then the connection is aborted, and the custom
/// callback is never called. You can customize the OS validation logic with a call to
/// IBackgroundCopyJobHttpOptions::SetSecurityFlags. For example, you can ignore expected certificate validation errors.
/// </para>
/// <para>
/// If OS validation passes, then the <see cref="IBackgroundCopyServerCertificateValidationCallback.ValidateServerCertificate"/>
/// method is called before completing the TLS handshake and before the HTTP request is sent.
/// </para>
/// <para>
/// If your validation method declines the certificate, the job will transition to <c>BG_JOB_STATE_TRANSIENT_ERROR</c> with a job
/// error context of <c>BG_ERROR_CONTEXT_SERVER_CERTIFICATE_CALLBACK</c> and the error <c>HRESULT</c> from your callback. If your
/// callback couldn't be called (for example, because BITS needed to validate a server certificate after your program exited), then
/// the job error code will be <c>BG_E_SERVER_CERT_VALIDATION_INTERFACE_REQUIRED</c>. When your application is next run, it can fix
/// this error by setting the validation callback again and resuming the job.
/// </para>
/// </remarks>
public void SetServerCertificateValidationInterface(IBackgroundCopyServerCertificateValidationCallback callback) =>
RunAction(() => IHttpOp3.SetServerCertificateValidationInterface(callback));
/// <summary>
/// Use the Suspend method to suspend a job. New jobs, jobs that are in error, and jobs that have finished transferring files are
/// automatically suspended.
@ -748,29 +829,15 @@ namespace Vanara.IO
private object GetProperty(BITS_JOB_PROPERTY_ID id)
{
var value = RunAction(() => IJob5.GetProperty(id));
switch (id)
return id switch
{
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_MAX_DOWNLOAD_SIZE:
return value.Uint64;
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_ID_COST_FLAGS:
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_MINIMUM_NOTIFICATION_INTERVAL_MS:
return value.Dword;
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_NOTIFICATION_CLSID:
return value.ClsID;
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_DYNAMIC_CONTENT:
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_HIGH_PERFORMANCE:
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_ON_DEMAND_MODE:
return value.Enable;
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_USE_STORED_CREDENTIALS:
return value.Target;
default:
throw new ArgumentOutOfRangeException(nameof(id));
}
BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_MAX_DOWNLOAD_SIZE => value.Uint64,
BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_ID_COST_FLAGS or BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_MINIMUM_NOTIFICATION_INTERVAL_MS => value.Dword,
BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_NOTIFICATION_CLSID => value.ClsID,
BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_DYNAMIC_CONTENT or BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_HIGH_PERFORMANCE or BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_ON_DEMAND_MODE => value.Enable,
BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_USE_STORED_CREDENTIALS => value.Target,
_ => throw new ArgumentOutOfRangeException(nameof(id)),
};
}
private void HandleCOMException(COMException cex)

View File

@ -45,7 +45,7 @@ namespace Vanara.IO
get
{
var job = BackgroundCopyManager.GetJob(jobId);
return job != null ? new BackgroundCopyJob(job) : throw new KeyNotFoundException();
return job is not null ? new BackgroundCopyJob(job) : throw new KeyNotFoundException();
}
}
@ -85,7 +85,7 @@ namespace Vanara.IO
try
{
var ijob = BackgroundCopyManager.GetJob(jobId);
return ijob != null;
return ijob is not null;
}
catch
{
@ -107,7 +107,7 @@ namespace Vanara.IO
/// cref="ICollection{T}" />. </returns> <exception cref="ArgumentNullException">item</exception>
public bool Remove(BackgroundCopyJob item)
{
if (item == null) throw new ArgumentNullException(nameof(item));
if (item is null) throw new ArgumentNullException(nameof(item));
// TODO: Look at what needs to be done to really remove a job and all it's actions
try { item.Cancel(); return true; } catch { return false; }
}
@ -160,7 +160,7 @@ namespace Vanara.IO
/// <summary>
/// Gets the <see cref="BackgroundCopyJob"/> object in the <see cref="BackgroundCopyJobCollection"/> collection to which the enumerator is pointing.
/// </summary>
public BackgroundCopyJob Current => icurrentjob != null ? new BackgroundCopyJob(icurrentjob) : throw new InvalidOperationException();
public BackgroundCopyJob Current => icurrentjob is not null ? new BackgroundCopyJob(icurrentjob) : throw new InvalidOperationException();
/// <summary>
/// Gets the <see cref="BackgroundCopyJob"/> object in the <see cref="BackgroundCopyJobCollection"/> collection to which the enumerator is pointing.
@ -181,7 +181,7 @@ namespace Vanara.IO
try
{
icurrentjob = ienum.Next(1)?.FirstOrDefault();
return icurrentjob != null;
return icurrentjob is not null;
}
catch { return false; }
}

View File

@ -71,7 +71,7 @@ namespace Vanara.IO
/// <summary>Gets a value indicating whether the <see cref="ICollection{T}"/> is read-only.</summary>
bool ICollection<BackgroundCopyJobCredential>.IsReadOnly => false;
private Dictionary<uint, BackgroundCopyJobCredential> Values => dict ?? (dict = new Dictionary<uint, BackgroundCopyJobCredential>());
private Dictionary<uint, BackgroundCopyJobCredential> Values => dict ??= new Dictionary<uint, BackgroundCopyJobCredential>();
/// <summary>Gets the <see cref="BackgroundCopyJobCredential"/> with the specified scheme and target.</summary>
/// <param name="scheme">The credential scheme.</param>
@ -102,7 +102,7 @@ namespace Vanara.IO
/// <summary>Removes all items from the <see cref="ICollection{T}"/>.</summary>
public void Clear()
{
if (dict == null) return;
if (dict is null) return;
foreach (var key in Values.Keys)
Remove(Values[key]);
}
@ -110,7 +110,7 @@ namespace Vanara.IO
/// <summary>Determines whether the <see cref="ICollection{T}"/> contains a specific value.</summary>
/// <param name="item">The object to locate in the <see cref="ICollection{T}"/>.</param>
/// <returns>true if <paramref name="item"/> is found in the <see cref="ICollection{T}"/>; otherwise, false.</returns>
public bool Contains(BackgroundCopyJobCredential item) => dict == null ? false : Values.ContainsKey(item.Key);
public bool Contains(BackgroundCopyJobCredential item) => dict is not null && Values.ContainsKey(item.Key);
/// <summary>
/// Copies the elements of the <see cref="ICollection{T}"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
@ -122,7 +122,7 @@ namespace Vanara.IO
/// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
public void CopyTo(BackgroundCopyJobCredential[] array, int arrayIndex)
{
if (dict == null) return;
if (dict is null) return;
Array.Copy(Values.Values.ToArray(), 0, array, arrayIndex, Count);
}
@ -139,7 +139,7 @@ namespace Vanara.IO
try
{
ijob2.RemoveCredentials((BG_AUTH_TARGET)target, (BG_AUTH_SCHEME)scheme);
if (dict != null)
if (dict is not null)
Values.Remove(MakeKey(scheme, target));
return true;
}

View File

@ -26,14 +26,16 @@ namespace Vanara.IO
{
get
{
try { return ver ?? (ver = GetVer()); }
try { return ver ??= GetVer(); }
catch { return new Version(); }
static Version GetVer()
{
var fi = System.Diagnostics.FileVersionInfo.GetVersionInfo(Environment.ExpandEnvironmentVariables(@"%WinDir%\Sysnative\qmgr.dll"));
var fi = System.Diagnostics.FileVersionInfo.GetVersionInfo(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "qmgr.dll"));
return $"{fi.FileMajorPart}.{fi.FileMinorPart}" switch
{
"7.8" when fi.FileBuildPart >= 18362 => new Version(10, 3),
"7.8" when fi.FileBuildPart >= 17763 => new Version(10, 2),
"7.8" => new Version(10, 1),
"7.7" => new Version(5, 0),
"7.5" => new Version(4, 0),