-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMdfVersionDetector.cs
More file actions
138 lines (122 loc) · 4.84 KB
/
Copy pathMdfVersionDetector.cs
File metadata and controls
138 lines (122 loc) · 4.84 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
using System;
using System.Collections.Generic;
using System.IO;
namespace SQLBakVersion.Class
{
/// <summary>
/// Version values stored in an SQL Server MDF boot page.
/// </summary>
public sealed class MdfVersion
{
public static readonly MdfVersion Unknown = new MdfVersion(null, null, null, null);
public MdfVersion(int? year, int? internalVersion, int? createVersion, string failureReason)
{
Year = year;
InternalVersion = internalVersion;
CreateVersion = createVersion;
FailureReason = failureReason;
}
public int? Year { get; private set; }
public int? InternalVersion { get; private set; }
public int? CreateVersion { get; private set; }
public string FailureReason { get; private set; }
public static MdfVersion Unavailable(string failureReason)
{
return new MdfVersion(null, null, null, failureReason);
}
}
/// <summary>
/// Detects the SQL Server version of an MDF file from its boot page.
/// </summary>
public static class MdfVersionDetector
{
private const int PageSize = 8192;
private const int BootPageNumber = 9;
private const int PageHeaderSize = 96;
private const int DbiVersionOffset = 4;
private const int DbiCreateVersionOffset = 6;
private const long DbiVersionFileOffset = (long)BootPageNumber * PageSize + PageHeaderSize + DbiVersionOffset;
private const long DbiCreateVersionFileOffset = (long)BootPageNumber * PageSize + PageHeaderSize + DbiCreateVersionOffset;
private static readonly IDictionary<int, int> VersionYears = new Dictionary<int, int>
{
{ 539, 2000 },
{ 611, 2005 },
{ 612, 2005 },
{ 655, 2008 },
{ 660, 2008 },
{ 661, 2008 },
{ 684, 2012 },
{ 706, 2012 },
{ 782, 2014 },
{ 852, 2016 },
{ 868, 2017 },
{ 869, 2017 },
{ 895, 2019 },
{ 896, 2019 },
{ 897, 2019 },
{ 902, 2019 },
{ 904, 2019 },
{ 950, 2022 },
{ 957, 2022 }
};
public static MdfVersion Detect(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
return MdfVersion.Unavailable("No MDF file was selected.");
}
try
{
using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
if (stream.Length < DbiCreateVersionFileOffset + sizeof(ushort))
{
return MdfVersion.Unavailable("The file is too small to contain the MDF boot page.");
}
stream.Position = DbiVersionFileOffset;
byte[] bytes = new byte[sizeof(ushort) * 2];
if (!ReadExactly(stream, bytes))
{
return MdfVersion.Unavailable("The MDF boot page could not be read completely.");
}
int internalVersion = ToUInt16LittleEndian(bytes, 0);
int createVersion = ToUInt16LittleEndian(bytes, sizeof(ushort));
int year;
return VersionYears.TryGetValue(internalVersion, out year)
? new MdfVersion(year, internalVersion, createVersion, null)
: new MdfVersion(null, internalVersion, createVersion, null);
}
}
catch (UnauthorizedAccessException exception)
{
return MdfVersion.Unavailable("The MDF file cannot be accessed: " + exception.Message);
}
catch (IOException exception)
{
return MdfVersion.Unavailable("The MDF file cannot be read. If it is attached to SQL Server, stop the service or use a copy of the file. " + exception.Message);
}
catch (Exception exception)
{
return MdfVersion.Unavailable("Unable to read the MDF boot page: " + exception.Message);
}
}
private static bool ReadExactly(Stream stream, byte[] bytes)
{
int totalBytesRead = 0;
while (totalBytesRead < bytes.Length)
{
int bytesRead = stream.Read(bytes, totalBytesRead, bytes.Length - totalBytesRead);
if (bytesRead == 0)
{
return false;
}
totalBytesRead += bytesRead;
}
return true;
}
private static int ToUInt16LittleEndian(byte[] bytes, int offset)
{
return bytes[offset] | (bytes[offset + 1] << 8);
}
}
}