Skip to content

Commit 1726bef

Browse files
committed
🔨 chore: update file handling to support .mdf files and improve UI text
1 parent f2ae1f9 commit 1726bef

6 files changed

Lines changed: 184 additions & 14 deletions

File tree

‎Main.Designer.cs‎

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Main.cs‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@ private void btnClose_Click(object sender, EventArgs e)
4949

5050
private void pnlDragDrop_Click(object sender, EventArgs e)
5151
{
52-
SelectBakFile();
52+
SelectDatabaseFile();
5353
}
5454

5555
private void Main_DragEnter(object sender, DragEventArgs e)
5656
{
5757
if (e.Data.GetDataPresent(DataFormats.FileDrop))
5858
{
5959
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
60-
if (files.Length > 0 && Path.GetExtension(files[0]).ToLower() == ".bak")
60+
if (files.Length > 0 && IsSupportedFile(files[0]))
6161
{
6262
e.Effect = DragDropEffects.Copy;
6363
return;
@@ -69,23 +69,23 @@ private void Main_DragEnter(object sender, DragEventArgs e)
6969
private void Main_DragDrop(object sender, DragEventArgs e)
7070
{
7171
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
72-
if (files.Length > 0 && Path.GetExtension(files[0]).ToLower() == ".bak")
72+
if (files.Length > 0 && IsSupportedFile(files[0]))
7373
{
74-
ProcessBakFile(files[0]);
74+
ProcessDatabaseFile(files[0]);
7575
}
7676
}
7777

78-
private void SelectBakFile()
78+
private void SelectDatabaseFile()
7979
{
8080
OpenFileDialog openFileDialog = new OpenFileDialog();
81-
openFileDialog.Filter = "SQL Server backup files (*.bak)|*.bak";
81+
openFileDialog.Filter = "SQL Server files (*.bak;*.mdf)|*.bak;*.mdf|SQL Server backup files (*.bak)|*.bak|SQL Server database files (*.mdf)|*.mdf";
8282
if (openFileDialog.ShowDialog() == DialogResult.OK)
8383
{
84-
ProcessBakFile(openFileDialog.FileName);
84+
ProcessDatabaseFile(openFileDialog.FileName);
8585
}
8686
}
8787

88-
private void ProcessBakFile(string filePath)
88+
private void ProcessDatabaseFile(string filePath)
8989
{
9090
lblDragDrop.Text = Path.GetFileName(filePath);
9191

@@ -94,5 +94,12 @@ private void ProcessBakFile(string filePath)
9494
lblVersion.ForeColor = versionInfo.StartsWith("SQL") ? Color.LightGreen : Color.Red;
9595
lblVersion.Visible = true;
9696
}
97+
98+
private static bool IsSupportedFile(string filePath)
99+
{
100+
string extension = Path.GetExtension(filePath);
101+
return string.Equals(extension, ".bak", StringComparison.OrdinalIgnoreCase)
102+
|| string.Equals(extension, ".mdf", StringComparison.OrdinalIgnoreCase);
103+
}
97104
}
98105
}

‎MdfVersionDetector.cs‎

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
5+
namespace SQLBakVersion.Class
6+
{
7+
/// <summary>
8+
/// Version values stored in an SQL Server MDF boot page.
9+
/// </summary>
10+
public sealed class MdfVersion
11+
{
12+
public static readonly MdfVersion Unknown = new MdfVersion(null, null, null, null);
13+
14+
public MdfVersion(int? year, int? internalVersion, int? createVersion, string failureReason)
15+
{
16+
Year = year;
17+
InternalVersion = internalVersion;
18+
CreateVersion = createVersion;
19+
FailureReason = failureReason;
20+
}
21+
22+
public int? Year { get; private set; }
23+
public int? InternalVersion { get; private set; }
24+
public int? CreateVersion { get; private set; }
25+
public string FailureReason { get; private set; }
26+
27+
public static MdfVersion Unavailable(string failureReason)
28+
{
29+
return new MdfVersion(null, null, null, failureReason);
30+
}
31+
}
32+
33+
/// <summary>
34+
/// Detects the SQL Server version of an MDF file from its boot page.
35+
/// </summary>
36+
public static class MdfVersionDetector
37+
{
38+
private const int PageSize = 8192;
39+
private const int BootPageNumber = 9;
40+
private const int PageHeaderSize = 96;
41+
private const int DbiVersionOffset = 4;
42+
private const int DbiCreateVersionOffset = 6;
43+
private const long DbiVersionFileOffset = (long)BootPageNumber * PageSize + PageHeaderSize + DbiVersionOffset;
44+
private const long DbiCreateVersionFileOffset = (long)BootPageNumber * PageSize + PageHeaderSize + DbiCreateVersionOffset;
45+
46+
private static readonly IDictionary<int, int> VersionYears = new Dictionary<int, int>
47+
{
48+
{ 539, 2000 },
49+
{ 611, 2005 },
50+
{ 612, 2005 },
51+
{ 655, 2008 },
52+
{ 660, 2008 },
53+
{ 661, 2008 },
54+
{ 684, 2012 },
55+
{ 706, 2012 },
56+
{ 782, 2014 },
57+
{ 852, 2016 },
58+
{ 868, 2017 },
59+
{ 869, 2017 },
60+
{ 895, 2019 },
61+
{ 896, 2019 },
62+
{ 897, 2019 },
63+
{ 902, 2019 },
64+
{ 904, 2019 },
65+
{ 950, 2022 },
66+
{ 957, 2022 }
67+
};
68+
69+
public static MdfVersion Detect(string path)
70+
{
71+
if (string.IsNullOrWhiteSpace(path))
72+
{
73+
return MdfVersion.Unavailable("No MDF file was selected.");
74+
}
75+
76+
try
77+
{
78+
using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
79+
{
80+
if (stream.Length < DbiCreateVersionFileOffset + sizeof(ushort))
81+
{
82+
return MdfVersion.Unavailable("The file is too small to contain the MDF boot page.");
83+
}
84+
85+
stream.Position = DbiVersionFileOffset;
86+
byte[] bytes = new byte[sizeof(ushort) * 2];
87+
if (!ReadExactly(stream, bytes))
88+
{
89+
return MdfVersion.Unavailable("The MDF boot page could not be read completely.");
90+
}
91+
92+
int internalVersion = ToUInt16LittleEndian(bytes, 0);
93+
int createVersion = ToUInt16LittleEndian(bytes, sizeof(ushort));
94+
int year;
95+
96+
return VersionYears.TryGetValue(internalVersion, out year)
97+
? new MdfVersion(year, internalVersion, createVersion, null)
98+
: new MdfVersion(null, internalVersion, createVersion, null);
99+
}
100+
}
101+
catch (UnauthorizedAccessException exception)
102+
{
103+
return MdfVersion.Unavailable("The MDF file cannot be accessed: " + exception.Message);
104+
}
105+
catch (IOException exception)
106+
{
107+
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);
108+
}
109+
catch (Exception exception)
110+
{
111+
return MdfVersion.Unavailable("Unable to read the MDF boot page: " + exception.Message);
112+
}
113+
}
114+
115+
private static bool ReadExactly(Stream stream, byte[] bytes)
116+
{
117+
int totalBytesRead = 0;
118+
119+
while (totalBytesRead < bytes.Length)
120+
{
121+
int bytesRead = stream.Read(bytes, totalBytesRead, bytes.Length - totalBytesRead);
122+
if (bytesRead == 0)
123+
{
124+
return false;
125+
}
126+
127+
totalBytesRead += bytesRead;
128+
}
129+
130+
return true;
131+
}
132+
133+
private static int ToUInt16LittleEndian(byte[] bytes, int offset)
134+
{
135+
return bytes[offset] | (bytes[offset + 1] << 8);
136+
}
137+
}
138+
}

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# SQL Bak Version
22

3-
SQL Bak Version is a tool designed to analyze the header of SQL Server backup files (.bak) and determine the SQL Server version without the need for a preinstalled SQL Server instance. This tool is particularly useful for quickly identifying the SQL Server version of backup files without the need to restore them or rely on a SQL Server installation.
3+
SQL Bak Version analyzes SQL Server backup files (.bak) and database files (.mdf) to determine their SQL Server version without a preinstalled SQL Server instance. This is useful for identifying a file's version without restoring or attaching it.
44

55
## Features
6-
- Analyze the header of SQL Server backup files.
6+
- Analyze SQL Server backup (.bak) and database (.mdf) files.
77
- Determine the SQL Server version based on specific byte patterns within the file.
88
- Display the detected SQL Server version in a user-friendly manner.
99

‎SQLBakVersion.csproj‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
<Reference Include="System.Xml" />
5050
</ItemGroup>
5151
<ItemGroup>
52+
<Compile Include="MdfVersionDetector.cs" />
5253
<Compile Include="SQLVersion.cs" />
5354
<Compile Include="Main.cs">
5455
<SubType>Form</SubType>
@@ -87,4 +88,4 @@
8788
<Content Include="icon.ico" />
8889
</ItemGroup>
8990
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
90-
</Project>
91+
</Project>

‎SQLVersion.cs‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ public class SQLVersion
5656
/// <returns>SQL Server version or error message</returns>
5757
public string GetVersion(string filePath)
5858
{
59+
if (string.Equals(Path.GetExtension(filePath), ".mdf", StringComparison.OrdinalIgnoreCase))
60+
{
61+
return GetMdfVersion(filePath);
62+
}
63+
5964
try
6065
{
6166
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
@@ -86,6 +91,25 @@ public string GetVersion(string filePath)
8691
}
8792
}
8893

94+
/// <summary>
95+
/// Gets the SQL Server version recorded in an MDF boot page.
96+
/// </summary>
97+
private string GetMdfVersion(string filePath)
98+
{
99+
MdfVersion version = MdfVersionDetector.Detect(filePath);
100+
101+
if (!version.InternalVersion.HasValue)
102+
{
103+
return string.IsNullOrEmpty(version.FailureReason)
104+
? "SQL version information not found"
105+
: version.FailureReason;
106+
}
107+
108+
return version.Year.HasValue
109+
? string.Format("SQL Server {0}", version.Year.Value)
110+
: string.Format("Unknown SQL Server version (code: {0})", version.InternalVersion.Value);
111+
}
112+
89113
/// <summary>
90114
/// Validates if the file has a valid SQL BAK header
91115
/// </summary>

0 commit comments

Comments
 (0)