Standardized, reproducible SQL Server installations
following a company standard - fully automatic, domain-aware
Uwe Janke · Senior SQL Server DBA · dtcSoftware
Manual SQL Server installation - different every time, error-prone every time.
Manual effort per installation - without any post-configuration. With hardening, quickly 4+ hours
Drive layout, collation, TempDB size vary by operator. No versioned baseline
SA account active, max memory not set, Ola jobs missing - deviations only surface in production
HLB vs. CONTOSO: different collation, different drives, different sysadmin groups - maintained manually
Previously 57 individual SQL scripts installed manually afterward. No log. No error handling
A new environment? The previous DBA has left? - No evidence of what was installed and in what state
One tool, one configuration, one standard. Set the domain once in the INI - everything else runs automatically afterward.
No PowerShell knowledge needed. Domain-specific pre-filled values, all parameters visible at a glance
NTFS, MaxMemory, MAXDOP, TempDB, SA obfuscation, Ola jobs, company scripts - fully automatic
Add an INI block, no code changes. Every parameter can be overridden per domain
| Before | After |
|---|---|
| 1-4 hours manual | ~25-70 min. fully automatic |
| Different every time | Reproducible, versioned |
| Hardening forgotten | 17 steps guaranteed |
| 57 SQL scripts manually | Automatic, alphabetical |
| No domain logic | INI-based, zero-touch |
| Requirement | Note |
|---|---|
| Local administrator | On the target server |
| 5 drives | C/F/G/H/I - configurable via INI |
| Network share reachable | \\srv\SQLSources |
dbatools ≥ 2.0 | Installed automatically |
sqmSQLTool | Installed automatically |
| SQL Server ISO extracted | Prepared in the SQLSources share |
Pre-filled per domain - version, collation, drives, monitoring. Everything on a single form.
All phases run sequentially in a background thread - the GUI stays usable at all times.
robocopy \\srv\SQLSources\SQL2022 C:\SQLSources\SQL2022 /E /R:2 /W:5
Setup.exe and the CU patch run more stably from a local drive. Avoids network interruptions during setup
The CU file in \Updates\ is automatically used by Install-DbaInstance as the UpdateSourcePath
| Component | Installer |
|---|---|
| SSRS | SQLServerReportingServices.exe - standalone since 2017 |
| SSAS | Enabled in the SQL setup via the AS feature |
| SSMS | SSMS-Setup-ENU.exe - version-independent |
| SSIS | Feature IS in the SQL engine setup (no extra file) |
| TDP | setup.exe from \TDP\ - version-independent |
The tool passes all parameters fully from the INI + GUI to Install-DbaInstance. No INI file is created manually.
| Parameter | Source |
|---|---|
Version | GUI combo box |
Edition | GUI combo box |
InstanceName | GUI text box |
Collation | INI / domain override |
DataPath / LogPath | INI DiskLayout |
TempPath | INI DiskLayout |
BackupPath | INI DiskLayout |
UpdateSourcePath | \SQL_Install\Updates\ auto |
ProductID | INI SerialNumbers |
SqlSvcAccount | GUI input |
Features | INI + GUI checkboxes |
# Detect the slipstream CU automatically $cuPath = Join-Path $localSrc 'SQL_Install\Updates' $cuFile = Get-ChildItem $cuPath '*.exe' ` -ErrorAction SilentlyContinue | Select-Object -First 1 # Install-DbaInstance call (excerpt) $params = @{ SqlInstance = 'localhost' Version = $ver Edition = $edition Path = $localSrc UpdateSourcePath = if ($cuFile) { $cuPath } else { $null } Collation = $collation DataPath = $paths.Data LogPath = $paths.Log TempPath = $paths.TempDB BackupPath = $paths.Backup Features = $features SqlSvcAccount = $svcAccount SqlSvcPassword = $svcPassword ProductID = $productKey PerformVolumeMaintenanceTasks = $true } Install-DbaInstance @params -Confirm:$false
PerformVolumeMaintenanceTasks = $true - lets SQL Server allocate volume space without zero-filling. Noticeably faster for file operations.
The tool detects the domain automatically at startup and loads domain-specific defaults from the INI - with no code changes.
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name - the NetBIOS name is extracted
First [DiskLayout_HLB], then [DiskLayout_Standard] as a fallback
Domain_HLB = SQL_Latin1... - per-key domain-specific or a standard fallback
No PowerShell changes. Just add a new [DiskLayout_XYZ] block to settings.ini
# settings.ini (excerpt) [DiskLayout_Standard] DataDrive = G LogDrive = H TempDrive = I BackupDrive = F InstallDrive= C [Collations] Standard = Latin1_General_CI_AS Domain_HLB = SQL_Latin1_General_CP1_CI_AS Domain_CONTOSO = Latin1_General_CI_AS [SysadminGroups] Domain_HLB = HLB\Fg_DC_SqlAdminAll_Mod Domain_CONTOSO = CONTOSO\Rg_SQL_Sysadmin Standard = # empty = no sysadmin [Monitoring] Domain_HLB = 2 # Full Domain_CONTOSO = 1 # Standard DefaultType = 1
Fully automatic after the installation. Every step logged, every error caught.
Previously 57 individual SQL scripts installed manually afterward. Today largely covered by sqmSQLTool - the rest runs automatically.
| Before (script) | Today (sqmSQLTool) |
|---|---|
010_maxmemory.sql | PostInstall step 2 |
020_maxdop.sql | PostInstall step 5 |
030_ntfs.sql | Invoke-sqmNtfsSetup |
040_sa_rename.sql | Invoke-sqmSaObfuscation |
050_ola_jobs.sql | New-sqmOlaMaintenanceJobs |
| Company logins, linked server | Step 17 (remaining) |
\\srv\SQLSources\Scripts\ - used automatically when SqlScriptsPath is empty in settings.ini.
# Invoke-SqlScriptFolder - flow $scripts = Get-ChildItem '\\srv\SQLSources\Scripts' ` -Filter '*.sql' | Sort-Object Name # Alphabetical: 001_ through 057_ foreach ($script in $scripts) { $sql = Get-Content $script.FullName -Raw # GO separator: split into batches $batches = $sql -split '\r?\nGO\r?\n|\r?\nGO$' | Where-Object { $_.Trim() -ne '' } foreach ($batch in $batches) { try { Invoke-DbaQuery -SqlInstance $inst ` -Query $batch -ErrorAction Stop log " OK: $($script.Name)" } catch { log " WARN: $($script.Name) - $_" # ContinueOnError - move to the next script } } }
001_logins.sql, 002_linkedserver.sql - the order is controlled via the filename.
The SA account is the best-known attack target on SQL Server. The tool obfuscates it automatically - safely, with no manual intervention.
Domain-specific group from the INI. At least one group must be assigned - otherwise no sysadmin access remains after renaming SA
Invoke-sqmSaObfuscation: a random name, a random 32-character password. Name + password only appear in the return object - keep them safe
| Attack | Protection |
|---|---|
Brute force on sa | Login is no longer named sa |
| Default password | 32-character random password |
| SA stays active | Login is locked (disabled) |
| No admin access | AD group set before renaming |
[SysadminGroups] empty = SA obfuscation is skipped. Recommendation: always configure an AD group.
Get-sqmSQLInstanceCheck checks after all PostInstall steps whether the instance is healthy.
The Setup Tool sets up the foundation. sqmSQLTool then takes over day-to-day operations - AlwaysOnSetup handles the HA configuration.
New-sqmOlaUsrDbBackupJob - FULL/DIFF/LOG jobs. Already created in PostInstall step 15
Invoke-sqmSplunkConfiguration - monitoring key + Splunk forwarder. PostInstall step 16
Deadlock analysis, health checks, certificate management - all after the initial setup
AlwaysOnSetup requires SQL Server to be installed identically on all nodes - exactly what the Setup Tool delivers
1. SQLSetupTool on node 1 + node 2 → 2. AlwaysOnSetup for AG configuration → 3. sqmSQLTool for ongoing operations
What the tool deliberately does not do - and why.
| Task | Responsibility |
|---|---|
| Drive assignment (assigning letters D:/E:/F:) | Server team |
| Install Windows Server | Server team |
| DNS records | Network team |
| Migrate production databases | SQLMigration Tool |
| Configure AlwaysOn | AlwaysOnSetup Tool |
| Firewall rules | Network team |
InplaceUpDate.
[PreInstall] Format64kCheck).
| Problem | Cause | Solution |
|---|---|---|
Install-DbaInstance timeout |
Setup takes longer than expected (CU patch) | dbatools waits actively - no manual intervention. For a real timeout: check the log under %TEMP%\dbatools |
| Robocopy failed | Share unreachable or no write permission on the local drive | Check the UNC path in settings.ini. Drive C: must have enough space (~10 GB) |
| SA obfuscation skipped | No sysadmin group configured in [SysadminGroups] |
Add a domain entry to settings.ini. SA then stays active - secure it manually |
| SQL script fails (step 17) | Syntax error, missing object, permission issue in a company script | ContinueOnError: remaining scripts keep running. Failures show up as WARN in the log |
| Wrong collation set | Domain not detected - the standard fallback was used | Check domain detection in the log. Add a Domain_XYZ entry to settings.ini if needed |
| dbatools import failed | PSGallery unreachable, ShareBasePath missing | Configure an offline copy under [dbatools] ShareBasePath in settings.ini |
4 phases fully automatic - Robocopy, Install-DbaInstance, components, hardening
Add an INI block - no PowerShell code changes. Ready to use immediately
NTFS, MaxMemory, MAXDOP, TempDB, SA obfuscation, Ola jobs, company scripts
sqmSQLTool covers the majority. The rest runs automatically as step 17
A consistent base for AlwaysOnSetup, sqmSQLTool, SQLMigration
| GitHub | github.com/JankeUwe/SQLSetupTool |
| Source structure | New-SqlSourceStructure.ps1 |
| Website | www.powershelldba.de |