diff --git a/Installer.Core/InstallationService.cs b/Installer.Core/InstallationService.cs
index ded2cb18..258dd13d 100644
--- a/Installer.Core/InstallationService.cs
+++ b/Installer.Core/InstallationService.cs
@@ -300,9 +300,32 @@ await CleanInstallAsync(connectionString, progress, cancellationToken)
return true;
}
+ ///
+ /// Token in 01_install_database.sql that the installer replaces with
+ /// SET statements supplying custom data/log file paths (issue #768).
+ /// When left untouched it is an inert SQL comment and the SERVERPROPERTY
+ /// defaults are used.
+ ///
+ private const string FilePathOverrideToken = "/*__PM_FILE_PATH_OVERRIDES__*/";
+
///
/// Execute SQL installation files from the given ScriptProvider.
///
+ ///
+ /// Optional server-side directory for the PerformanceMonitor data file.
+ /// When supplied, it overrides SERVERPROPERTY('InstanceDefaultDataPath')
+ /// on first creation of the database. Ignored if the database already
+ /// exists. (Placed after cancellationToken so existing callers that pass
+ /// the token positionally keep compiling.)
+ ///
+ ///
+ /// Optional server-side directory for the PerformanceMonitor log file.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage(
+ "Design",
+ "CA1068:CancellationToken parameters must come last",
+ Justification = "dataPath/logPath are appended after cancellationToken so existing " +
+ "callers that pass the token positionally (e.g. Dashboard AddServerDialog) keep compiling.")]
public static async Task ExecuteInstallationAsync(
string connectionString,
ScriptProvider provider,
@@ -310,7 +333,9 @@ public static async Task ExecuteInstallationAsync(
bool resetSchedule = false,
IProgress? progress = null,
Func? preValidationAction = null,
- CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default,
+ string? dataPath = null,
+ string? logPath = null)
{
var scriptFiles = provider.GetInstallFiles();
ArgumentNullException.ThrowIfNull(scriptFiles);
@@ -410,6 +435,24 @@ Files execute without transaction wrapping because many contain DDL.
});
}
+ /*Inject custom data/log file paths into the CREATE DATABASE step (#768).
+ Only applies on first creation; if the database already exists the
+ guarded block is skipped, so the paths are silently ignored.*/
+ if (fileName.StartsWith("01_", StringComparison.Ordinal) &&
+ (!string.IsNullOrWhiteSpace(dataPath) || !string.IsNullOrWhiteSpace(logPath)))
+ {
+ sqlContent = sqlContent.Replace(
+ FilePathOverrideToken,
+ BuildFilePathOverrideSql(dataPath, logPath),
+ StringComparison.Ordinal);
+
+ progress?.Report(new InstallationProgress
+ {
+ Message = "Applying custom database file path(s) to CREATE DATABASE...",
+ Status = "Info"
+ });
+ }
+
/*Remove SQLCMD directives*/
sqlContent = Patterns.SqlCmdDirectivePattern.Replace(sqlContent, "");
@@ -501,6 +544,58 @@ Files execute without transaction wrapping because many contain DDL.
return result;
}
+ ///
+ /// Builds the T-SQL that sets the override path variables in
+ /// 01_install_database.sql. Each path is normalized to end with a
+ /// separator and single quotes are doubled so the value cannot break out
+ /// of the surrounding N'...' literal. The install script applies a second
+ /// REPLACE(...) escape when it concatenates the value into the dynamic
+ /// CREATE DATABASE statement (defense in depth).
+ ///
+ private static string BuildFilePathOverrideSql(string? dataPath, string? logPath)
+ {
+ var sb = new StringBuilder();
+
+ if (!string.IsNullOrWhiteSpace(dataPath))
+ {
+ AppendPathOverride(sb, "@data_path_override", dataPath);
+ }
+
+ if (!string.IsNullOrWhiteSpace(logPath))
+ {
+ AppendPathOverride(sb, "@log_path_override", logPath);
+ }
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Appends a "SET @override = N'...'" statement for a validated path.
+ /// Re-validates so the no-control-character / absolute-path guarantees hold
+ /// for any caller (not just the CLI, which already validates), then escapes
+ /// the value before embedding it in the single-quoted literal.
+ ///
+ private static void AppendPathOverride(StringBuilder sb, string variableName, string path)
+ {
+ if (!PathValidation.TryValidateDirectory(path, out string normalized, out string error))
+ {
+ throw new ArgumentException($"Invalid database file path '{path}': {error}", nameof(path));
+ }
+
+ sb.Append("SET ")
+ .Append(variableName)
+ .Append(" = N'")
+ .Append(EscapeSqlStringLiteral(normalized))
+ .Append("';\n ");
+ }
+
+ ///
+ /// Doubles single quotes so a value can be embedded safely inside a
+ /// single-quoted T-SQL string literal.
+ ///
+ private static string EscapeSqlStringLiteral(string value) =>
+ value.Replace("'", "''", StringComparison.Ordinal);
+
///
/// Run validation (master collector) after installation.
///
diff --git a/Installer.Core/PathValidation.cs b/Installer.Core/PathValidation.cs
new file mode 100644
index 00000000..fc6e8554
--- /dev/null
+++ b/Installer.Core/PathValidation.cs
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2026 Erik Darling, Darling Data LLC
+ *
+ * This file is part of the SQL Server Performance Monitor.
+ *
+ * Licensed under the MIT License. See LICENSE file in the project root for full license information.
+ */
+
+using System.Buffers;
+
+namespace Installer.Core;
+
+///
+/// Validates and normalizes user-supplied database file directory paths
+/// (the --data-path / --log-path installer options, issue #768).
+///
+/// The path is a SERVER-SIDE directory: it is where SQL Server places the
+/// PerformanceMonitor data/log files, so it is validated for shape only
+/// (absolute, no illegal characters, sane length). It is deliberately NOT
+/// checked for existence on the machine running the installer, which may be
+/// a different host than the SQL Server.
+///
+public static class PathValidation
+{
+ ///
+ /// Maximum directory length. Leaves room for the appended file name
+ /// (e.g., "PerformanceMonitor_log.ldf") inside the nvarchar(512) variable.
+ ///
+ private const int MaxDirectoryLength = 480;
+
+ ///
+ /// Characters that are never valid in a Windows path and that we also
+ /// reject for Linux targets as a defensive measure. The single quote is
+ /// deliberately allowed (it is legal in a Windows path, e.g. C:\Bob's Data\)
+ /// and is escaped before it reaches T-SQL.
+ ///
+ private static readonly SearchValues ForbiddenCharacters =
+ SearchValues.Create("\"<>|*?");
+
+ ///
+ /// Validates a user-supplied directory path and, on success, returns a
+ /// normalized form that ends with a path separator.
+ ///
+ /// Raw path from the command line.
+ /// Normalized path (with trailing separator) when valid.
+ /// Human-readable reason when invalid.
+ /// True when the path is acceptable.
+ public static bool TryValidateDirectory(string? input, out string normalized, out string error)
+ {
+ normalized = string.Empty;
+ error = string.Empty;
+
+ if (string.IsNullOrWhiteSpace(input))
+ {
+ error = "path is empty.";
+ return false;
+ }
+
+ string path = input.Trim();
+
+ if (path.Length > MaxDirectoryLength)
+ {
+ error = $"path exceeds {MaxDirectoryLength} characters.";
+ return false;
+ }
+
+ /*Reject control characters (includes CR/LF/tab) so a path can't smuggle
+ extra statements or break the surrounding T-SQL string literal.*/
+ foreach (char c in path)
+ {
+ if (char.IsControl(c))
+ {
+ error = "path contains control characters.";
+ return false;
+ }
+ }
+
+ if (path.AsSpan().IndexOfAny(ForbiddenCharacters) >= 0)
+ {
+ error = "path contains invalid characters (\" < > | * ?).";
+ return false;
+ }
+
+ if (!IsAbsolute(path))
+ {
+ error = "path must be absolute (e.g., D:\\SQLData, \\\\server\\share, or /var/opt/mssql).";
+ return false;
+ }
+
+ normalized = EnsureTrailingSeparator(path);
+ return true;
+ }
+
+ ///
+ /// Returns true when the path is a fully-qualified Windows drive path
+ /// (C:\...), a UNC path (\\server\share), or a Linux absolute path (/...).
+ /// OS-independent on purpose: the installer is win-x64 but can target
+ /// SQL Server running on Linux.
+ ///
+ public static bool IsAbsolute(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ return false;
+ }
+
+ /*Windows drive: X:\ or X:/ */
+ if (path.Length >= 3 &&
+ char.IsLetter(path[0]) &&
+ path[1] == ':' &&
+ (path[2] == '\\' || path[2] == '/'))
+ {
+ return true;
+ }
+
+ /*UNC: \\server\share*/
+ if (path.StartsWith("\\\\", StringComparison.Ordinal))
+ {
+ return true;
+ }
+
+ /*Linux absolute: /var/...*/
+ return path[0] == '/';
+ }
+
+ ///
+ /// Ensures the directory path ends with a separator so a file name can be
+ /// appended. Uses the separator style already present in the path so Linux
+ /// targets keep forward slashes.
+ ///
+ public static string EnsureTrailingSeparator(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ return path;
+ }
+
+ if (path.EndsWith('\\') || path.EndsWith('/'))
+ {
+ return path;
+ }
+
+ /*A Linux-style path uses forward slashes; everything else uses backslashes.*/
+ char separator =
+ path.Contains('/') && !path.Contains('\\')
+ ? '/'
+ : '\\';
+
+ return path + separator;
+ }
+}
diff --git a/Installer.Tests/PathValidationTests.cs b/Installer.Tests/PathValidationTests.cs
new file mode 100644
index 00000000..5127c28f
--- /dev/null
+++ b/Installer.Tests/PathValidationTests.cs
@@ -0,0 +1,128 @@
+using Installer.Core;
+
+namespace Installer.Tests;
+
+///
+/// Unit tests for PathValidation — the shape checks and normalization applied
+/// to the --data-path / --log-path installer options (issue #768). These run
+/// without a database.
+///
+public class PathValidationTests
+{
+ [Theory]
+ [InlineData(@"C:\SQLData")]
+ [InlineData(@"D:\SQL Data\Monitor")]
+ [InlineData(@"C:/SQLData")]
+ [InlineData(@"\\fileserver\share\sql")]
+ [InlineData("/var/opt/mssql/data")]
+ [InlineData(@"C:\Bob's Data")]
+ public void TryValidateDirectory_AcceptsAbsolutePaths(string path)
+ {
+ bool ok = PathValidation.TryValidateDirectory(path, out string normalized, out string error);
+
+ Assert.True(ok, error);
+ Assert.NotEmpty(normalized);
+ }
+
+ [Theory]
+ [InlineData("SQLData")]
+ [InlineData(@"relative\path")]
+ [InlineData("data.mdf")]
+ public void TryValidateDirectory_RejectsRelativePaths(string path)
+ {
+ bool ok = PathValidation.TryValidateDirectory(path, out _, out string error);
+
+ Assert.False(ok);
+ Assert.Contains("absolute", error, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(" ")]
+ [InlineData(null)]
+ public void TryValidateDirectory_RejectsEmpty(string? path)
+ {
+ bool ok = PathValidation.TryValidateDirectory(path, out _, out string error);
+
+ Assert.False(ok);
+ Assert.NotEmpty(error);
+ }
+
+ [Theory]
+ [InlineData("C:\\SQL\nData")] // embedded newline (would break the T-SQL literal)
+ [InlineData("C:\\SQL\rData")] // embedded carriage return
+ [InlineData("C:\\SQL\tData")] // embedded tab
+ public void TryValidateDirectory_RejectsControlCharacters(string path)
+ {
+ bool ok = PathValidation.TryValidateDirectory(path, out _, out string error);
+
+ Assert.False(ok);
+ Assert.Contains("control", error, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Theory]
+ [InlineData("C:\\SQLData")]
+ [InlineData("C:\\SQL|Data")]
+ [InlineData("C:\\SQL*Data")]
+ [InlineData("C:\\SQL?Data")]
+ [InlineData("C:\\SQL\"Data")]
+ public void TryValidateDirectory_RejectsForbiddenCharacters(string path)
+ {
+ bool ok = PathValidation.TryValidateDirectory(path, out _, out string error);
+
+ Assert.False(ok);
+ Assert.Contains("invalid", error, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public void TryValidateDirectory_RejectsOverlyLongPath()
+ {
+ string longPath = @"C:\" + new string('a', 500);
+
+ bool ok = PathValidation.TryValidateDirectory(longPath, out _, out string error);
+
+ Assert.False(ok);
+ Assert.Contains("exceeds", error, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public void TryValidateDirectory_NormalizesTrailingSeparator_Windows()
+ {
+ bool ok = PathValidation.TryValidateDirectory(@"C:\SQLData", out string normalized, out _);
+
+ Assert.True(ok);
+ Assert.Equal(@"C:\SQLData\", normalized);
+ }
+
+ [Fact]
+ public void TryValidateDirectory_TrimsWhitespaceBeforeValidating()
+ {
+ bool ok = PathValidation.TryValidateDirectory(" C:\\SQLData ", out string normalized, out _);
+
+ Assert.True(ok);
+ Assert.Equal(@"C:\SQLData\", normalized);
+ }
+
+ [Theory]
+ [InlineData(@"C:\SQLData", @"C:\SQLData\")]
+ [InlineData(@"C:\SQLData\", @"C:\SQLData\")]
+ [InlineData("/var/opt/mssql", "/var/opt/mssql/")]
+ [InlineData("/var/opt/mssql/", "/var/opt/mssql/")]
+ [InlineData(@"\\srv\share", @"\\srv\share\")]
+ public void EnsureTrailingSeparator_UsesPathStyle(string input, string expected)
+ {
+ Assert.Equal(expected, PathValidation.EnsureTrailingSeparator(input));
+ }
+
+ [Theory]
+ [InlineData(@"C:\x", true)]
+ [InlineData(@"\\server\share", true)]
+ [InlineData("/etc/data", true)]
+ [InlineData(@"relative\x", false)]
+ [InlineData("plainword", false)]
+ public void IsAbsolute_ClassifiesCorrectly(string path, bool expected)
+ {
+ Assert.Equal(expected, PathValidation.IsAbsolute(path));
+ }
+}
diff --git a/Installer/Program.cs b/Installer/Program.cs
index e71b30ab..e7f8db49 100644
--- a/Installer/Program.cs
+++ b/Installer/Program.cs
@@ -46,6 +46,8 @@ static async Task Main(string[] args)
--reinstall Drop existing database and perform clean install
--encrypt=X Connection encryption: mandatory (default), optional, strict
--trust-cert Trust server certificate without validation (default: require valid cert)
+ --data-path DIR Server-side directory for the data (.mdf) file (first install only)
+ --log-path DIR Server-side directory for the log (.ldf) file (first install only)
*/
if (args.Any(a => a.Equals("--help", StringComparison.OrdinalIgnoreCase)
|| a.Equals("-h", StringComparison.OrdinalIgnoreCase)))
@@ -65,6 +67,8 @@ static async Task Main(string[] args)
Console.WriteLine(" --encrypt= Connection encryption: mandatory (default), optional, strict");
Console.WriteLine(" --trust-cert Trust server certificate without validation");
Console.WriteLine(" --entra Use Microsoft Entra ID interactive authentication (MFA)");
+ Console.WriteLine(" --data-path Server-side directory for the data (.mdf) file (first install only)");
+ Console.WriteLine(" --log-path Server-side directory for the log (.ldf) file (first install only)");
Console.WriteLine();
Console.WriteLine("Environment Variables:");
Console.WriteLine(" PM_SQL_PASSWORD SQL Auth password (avoids passing on command line)");
@@ -128,17 +132,57 @@ static async Task Main(string[] args)
}
}
+ /*Parse optional custom database file locations (#768).
+ Supports both --data-path= and --data-path (and --log-path).
+ These are server-side directories where SQL Server places the
+ PerformanceMonitor data/log files on first creation.*/
+ string? dataPathArg = GetOptionValue(args, "--data-path");
+ string? logPathArg = GetOptionValue(args, "--log-path");
+
+ string? dataPath = null;
+ string? logPath = null;
+
+ if (dataPathArg != null)
+ {
+ if (!PathValidation.TryValidateDirectory(dataPathArg, out dataPath, out string dataPathError))
+ {
+ Console.WriteLine($"Error: invalid --data-path: {dataPathError}");
+ return (int)InstallationResultCode.InvalidArguments;
+ }
+ }
+
+ if (logPathArg != null)
+ {
+ if (!PathValidation.TryValidateDirectory(logPathArg, out logPath, out string logPathError))
+ {
+ Console.WriteLine($"Error: invalid --log-path: {logPathError}");
+ return (int)InstallationResultCode.InvalidArguments;
+ }
+ }
+
+ if (dataPath != null)
+ {
+ Console.WriteLine($"Custom data file directory: {dataPath} (used only when the database is first created)");
+ }
+ if (logPath != null)
+ {
+ Console.WriteLine($"Custom log file directory: {logPath} (used only when the database is first created)");
+ }
+
/*Filter out all --flags and their trailing values to get positional arguments
- (server, username, password). Flags like --entra and --encrypt
- have a following value that must also be removed.*/
+ (server, username, password). Flags like --entra , --encrypt ,
+ --data-path , and --log-path have a following value that must also
+ be removed.*/
var filteredArgsList = new List();
for (int i = 0; i < args.Length; i++)
{
if (args[i].StartsWith("--", StringComparison.Ordinal))
{
- /*Skip flags that take a trailing value (--entra , --encrypt )*/
+ /*Skip flags that take a trailing value (space-separated form)*/
if ((args[i].Equals("--entra", StringComparison.OrdinalIgnoreCase)
- || args[i].Equals("--encrypt", StringComparison.OrdinalIgnoreCase))
+ || args[i].Equals("--encrypt", StringComparison.OrdinalIgnoreCase)
+ || args[i].Equals("--data-path", StringComparison.OrdinalIgnoreCase)
+ || args[i].Equals("--log-path", StringComparison.OrdinalIgnoreCase))
&& i + 1 < args.Length && !args[i + 1].StartsWith("--", StringComparison.Ordinal))
{
i++; /*skip the value too*/
@@ -232,6 +276,8 @@ Automated mode with command-line arguments
Console.WriteLine(" --reset-schedule Reset collection schedule to recommended defaults");
Console.WriteLine(" --encrypt= Connection encryption: mandatory (default), optional, strict");
Console.WriteLine(" --trust-cert Trust server certificate without validation (default: require valid cert)");
+ Console.WriteLine(" --data-path Server-side directory for the data (.mdf) file (first install only)");
+ Console.WriteLine(" --log-path Server-side directory for the log (.ldf) file (first install only)");
return (int)InstallationResultCode.InvalidArguments;
}
}
@@ -756,7 +802,10 @@ await dependencyInstaller.InstallDependenciesAsync(
Console.WriteLine($"Warning: Dependency installation encountered errors: {ex.Message}");
Console.WriteLine("Continuing with installation...");
}
- }).ConfigureAwait(false);
+ },
+ cancellationToken: default,
+ dataPath: dataPath,
+ logPath: logPath).ConfigureAwait(false);
installSuccessCount = installResult.FilesSucceeded;
installFailureCount = installResult.FilesFailed;
@@ -1107,6 +1156,29 @@ private static string WriteErrorLog(Exception ex, string serverName, string inst
return logPath;
}
+ /*
+ Read an option value supporting both "--opt=value" and "--opt value" forms.
+ Returns null when the option is absent or has no value. A value that
+ itself starts with "--" (i.e., the next flag) is treated as absent.
+ */
+ private static string? GetOptionValue(string[] args, string optionName)
+ {
+ string prefix = optionName + "=";
+ var equalsForm = args.FirstOrDefault(a => a.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
+ if (equalsForm != null)
+ {
+ return equalsForm.Substring(prefix.Length);
+ }
+
+ int index = Array.FindIndex(args, a => a.Equals(optionName, StringComparison.OrdinalIgnoreCase));
+ if (index >= 0 && index + 1 < args.Length && !args[index + 1].StartsWith("--", StringComparison.Ordinal))
+ {
+ return args[index + 1];
+ }
+
+ return null;
+ }
+
/*
Sanitize a string for use in a filename
Replaces invalid characters with underscores
diff --git a/README.md b/README.md
index 73f3076a..0a288075 100644
--- a/README.md
+++ b/README.md
@@ -184,6 +184,12 @@ PerformanceMonitorInstaller.exe YourServerName --reinstall
PerformanceMonitorInstaller.exe YourServerName sa YourPassword --reinstall
```
+Custom data/log file locations (applied only when the database is first created):
+
+```
+PerformanceMonitorInstaller.exe YourServerName --data-path D:\SQLData --log-path E:\SQLLogs
+```
+
Uninstall (removes database, Agent jobs, and XE sessions):
```
@@ -208,8 +214,12 @@ The installer automatically tests the connection, checks the SQL Server version
| `--preserve-jobs` | Keep existing SQL Agent job schedules during upgrade |
| `--encrypt=optional\|mandatory\|strict` | Connection encryption level (default: mandatory) |
| `--trust-cert` | Trust server certificate without validation (default: require valid cert) |
+| `--data-path DIR` | Server-side directory for the data (`.mdf`) file (used only on first install) |
+| `--log-path DIR` | Server-side directory for the log (`.ldf`) file (used only on first install) |
| `--help` | Show usage information and exit |
+> **Custom file locations:** `--data-path` / `--log-path` set where SQL Server places the PerformanceMonitor data and log files. They take effect **only when the database is first created** — if the database already exists they are ignored. Either flag may be supplied independently; an omitted one falls back to the instance default (`SERVERPROPERTY('InstanceDefaultDataPath')` / `InstanceDefaultLogPath`). The directory is a path **on the SQL Server host** and must already exist, with the SQL Server service account holding write permission. Both `--data-path D:\SQLData` and `--data-path=D:\SQLData` forms are accepted; quote paths containing spaces. Not applicable to Azure SQL Managed Instance, which always uses its managed file layout.
+
**Environment variable:** Set `PM_SQL_PASSWORD` to avoid passing the password on the command line.
### Exit Codes
diff --git a/install/01_install_database.sql b/install/01_install_database.sql
index cacd3224..179e43a6 100644
--- a/install/01_install_database.sql
+++ b/install/01_install_database.sql
@@ -37,9 +37,19 @@ BEGIN
DECLARE
@data_path nvarchar(512) = N'',
@log_path nvarchar(512) = N'',
+ @data_path_override nvarchar(512) = N'',
+ @log_path_override nvarchar(512) = N'',
@sql nvarchar(max) = N'',
@engine_edition integer = CONVERT(integer, SERVERPROPERTY(N'EngineEdition'));
+ /*
+ The installer injects SET statements for custom data/log file paths here
+ when the --data-path / --log-path options are supplied (#768). When this
+ script is run without those options (or outside the installer) the line
+ below stays an inert comment, so the SERVERPROPERTY defaults are used.
+ */
+ /*__PM_FILE_PATH_OVERRIDES__*/
+
/*
Azure SQL Managed Instance (engine edition 8) does not support
specifying files and filegroups in CREATE DATABASE.
@@ -65,17 +75,29 @@ BEGIN
@log_size_mb integer = 256;
/*
- Get the default data and log directories from instance properties
+ Use the installer-provided directories when supplied (#768);
+ otherwise fall back to the instance default data/log directories.
+ Override paths already carry a trailing separator.
*/
- SELECT
- @data_path =
+ IF LEN(@data_path_override) > 0
+ SET @data_path =
+ @data_path_override +
+ N'PerformanceMonitor.mdf';
+ ELSE
+ SET @data_path =
CONVERT
(
nvarchar(512),
SERVERPROPERTY(N'InstanceDefaultDataPath')
) +
- N'PerformanceMonitor.mdf',
- @log_path =
+ N'PerformanceMonitor.mdf';
+
+ IF LEN(@log_path_override) > 0
+ SET @log_path =
+ @log_path_override +
+ N'PerformanceMonitor_log.ldf';
+ ELSE
+ SET @log_path =
CONVERT
(
nvarchar(512),
@@ -128,7 +150,7 @@ BEGIN
ON PRIMARY
(
NAME = N''PerformanceMonitor'',
- FILENAME = N''' + @data_path + N''',
+ FILENAME = N''' + REPLACE(@data_path, N'''', N'''''') + N''',
SIZE = ' + CONVERT(nvarchar(20), @data_size_mb) + N'MB,
MAXSIZE = UNLIMITED,
FILEGROWTH = 1024MB
@@ -136,7 +158,7 @@ BEGIN
LOG ON
(
NAME = N''PerformanceMonitor_log'',
- FILENAME = N''' + @log_path + N''',
+ FILENAME = N''' + REPLACE(@log_path, N'''', N'''''') + N''',
SIZE = ' + CONVERT(nvarchar(20), @log_size_mb) + N'MB,
MAXSIZE = UNLIMITED,
FILEGROWTH = 64MB
@@ -203,7 +225,7 @@ BEGIN
ON PRIMARY
(
NAME = N''PerformanceMonitor'',
- FILENAME = N''' + @data_path + N''',
+ FILENAME = N''' + REPLACE(@data_path, N'''', N'''''') + N''',
SIZE = ' + CONVERT(nvarchar(20), @data_size_mb) + N'MB,
MAXSIZE = UNLIMITED,
FILEGROWTH = 1024MB
@@ -211,7 +233,7 @@ BEGIN
LOG ON
(
NAME = N''PerformanceMonitor_log'',
- FILENAME = N''' + @log_path + N''',
+ FILENAME = N''' + REPLACE(@log_path, N'''', N'''''') + N''',
SIZE = ' + CONVERT(nvarchar(20), @log_size_mb) + N'MB,
MAXSIZE = UNLIMITED,
FILEGROWTH = 64MB