-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathExtract-SSMSSavedCredentials.ps1
More file actions
265 lines (223 loc) · 10.4 KB
/
Extract-SSMSSavedCredentials.ps1
File metadata and controls
265 lines (223 loc) · 10.4 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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
#Requires -RunAsAdministrator
#Requires -Version 7.0
<#
.SYNOPSIS
Extracts and decrypts DPAPI-encrypted connection strings and credentials from SSMS privateregistry.bin files.
.DESCRIPTION
This script locates privateregistry.bin files from SSMS configuration folders, extracts the ConnectionMruList registry keys,
decrypts the DPAPI-protected connection strings, and writes the results to text files.
The script uses the current user's DPAPI key to decrypt the connection strings and credentials.
data within the decrypted strings.
Will not work with privateregistry.bin files from other machines or user profiles, as DPAPI encryption is tied to the user context.
.PARAMETER KeepRegFiles
A switch parameter that, when specified, prevents the script from deleting the intermediate .reg files created during the process.
By default, each .reg file is deleted after processing.
.EXAMPLE
.\Extract-SSMSSavedCredentials.ps1
Extracts and decrypts connection strings and credentials from all SSMS privateregistry.bin files found in the default SSMS
configuration folders. The results are saved to `DecryptedConnectionStrings.txt` and `DecryptedCredentials.txt` in the script's directory.
.EXAMPLE
.\Extract-SSMSSavedCredentials.ps1 -KeepRegFiles
Performs the same operation as the previous example, but retains the intermediate .reg files for further inspection.
.NOTES
- Requires admin rights.
- Works only for SSMS 21 and SSMS 22.
- Works only for the current user profile, as DPAPI encryption is user-specific.
- Requires PowerSell 7.0 or higher.
Author: Vlad Drumea (VladDBA)
Website: https://vladdba.com
Date: November 18, 2025
.INPUTS
None.
.OUTPUTS
The script writes the decrypted connection strings and credentials to text files in the script's directory.
- DecryptedConnectionStrings.txt: Contains the decrypted connection strings in the format:
Connection Name: Connection String
- DecryptedCredentials.txt: Contains the decrypted credentials - data source, user ID, and password.
.LINK
For more information, visit: https://vladdba.com/2025/11/22/powershell-extract-ssms-21-22-saved-connection-information/
#>
param (
[Parameter(Mandatory = $false)]
[switch]$KeepRegFiles
)
# helper function to turn a hex string into a byte array
function Convert-HexToByteArray {
param([string]$Hex)
$clean = $Hex -replace '\s', '' # strip any whitespace
if ($clean.Length % 2 -ne 0) {
throw "Hex string length must be even. Got $($clean.Length) characters."
}
$bytes = New-Object byte[] ($clean.Length / 2)
for ($i = 0; $i -lt $bytes.Length; $i++) {
$bytes[$i] = [Convert]::ToByte($clean.Substring($i * 2, 2), 16)
}
return $bytes
}
# a little helper function to invoke reg.exe commands
function Invoke-Reg {
param(
[Parameter(Mandatory)][string] $Command,
[Parameter(Mandatory)][string[]] $Arguments
)
$argsLine = $Arguments -join ' '
$proc = Start-Process -FilePath 'reg.exe' -ArgumentList $Command, $argsLine `
-NoNewWindow -PassThru -Wait -RedirectStandardError err.txt
if ($proc.ExitCode -ne 0) {
$err = Get-Content -Path err.txt -Raw
Remove-Item err.txt -Force
throw "reg $Command $argsLine failed (Exit $($proc.ExitCode)): $err"
exit
}
Remove-Item err.txt -Force
}
## internal variables
$BinFile = 'privateregistry.bin'
$MountPoint = 'HKEY_LOCAL_MACHINE\SSMSStuff'
$MountPointTest = 'HKLM:\SSMSStuff'
$ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
$RawConnStringPath = Join-Path -Path $ScriptPath -ChildPath 'DecryptedConnectionStrings.txt'
$CredentialsPath = Join-Path -Path $ScriptPath -ChildPath 'DecryptedCredentials.txt'
$ConnStrings = @()
$Credentials = @()
$TotalConnStrings = 0
$TotalCredentials = 0
# where SSMS 21 and 22 related configuration folders live
$SSMSRoot = Join-Path -Path $env:LOCALAPPDATA -ChildPath 'Microsoft\SSMS'
## checks and confirmations
Write-Host " Make sure all instances of SSMS are closed before proceeding" -Fore Yellow
$Confirmation = Read-Host -Prompt "Do you want to continue? (Y/N)"
if ($Confirmation -ne "Y") {
Write-Host " Operation cancelled by user."
exit
}
if (-not (Test-Path -Path $SSMSRoot)) {
throw "The SSMS root directory ($SSMSRoot) does not exist. Is SSMS even installed?"
}
# just to make sure SSMS is not running
while (Get-Process "SSMS" -ErrorAction SilentlyContinue | Where-Object { $_.FileVersion -match '^(21|22)\.' }) {
Write-Host " SSMS process detected. Please close all SSMS instances to proceed." -Fore Yellow
Read-Host -Prompt "Press Enter after all SSMS 21/22 processes have been closed"
}
## figure out if there's anything to work with
$MatchingDirs = Get-ChildItem -Path $SSMSRoot -Directory |
Where-Object {
Test-Path -Path (Join-Path $_.FullName $BinFile) -PathType Leaf
} |
Select-Object -ExpandProperty FullName
# did we find any matching dirs?
if ($MatchingDirs.Count -eq 0) {
throw "No SSMS configuration folders with privateregistry.bin files were found."
}
# how many matching dirs did we find?
$DirCount = $MatchingDirs.Count
if ($DirCount -eq 0) {
throw "Could not locate any SSMS configuration folders with privateregistry.bin files."
exit
}
Write-Host "`n Found $DirCount SSMS configuration folders with privateregistry.bin files." -Fore Green
# process each matching hive
foreach ($Folder in $MatchingDirs) {
$FName = Split-Path $Folder -Leaf # will use this as an identifier
$Hive = Join-Path -Path $Folder -ChildPath $BinFile
# have a quoted path for reg.exe
$HiveSafe = '"' + $Hive + '"'
# make sure there's no mounted hive from a previous run
if (Test-Path -Path $MountPointTest) {
Write-Host " Unloading previously mounted hive at $MountPoint..." -Fore Yellow
Invoke-Reg -Command 'unload' -Arguments @($MountPoint)
}
# Mount the hive
Write-Host "`n Mounting hive from $FName..." -Fore Magenta
Invoke-Reg -Command 'load' -Arguments @($MountPoint, $HiveSafe)
$RegFileName = "${FName}_ConnectionMruList.reg"
$RegFilePath = Join-Path -Path $ScriptPath -ChildPath $RegFileName
# have a quoted path for reg.exe
$RegFilePathSafe = '"' + $RegFilePath + '"'
$ExportPath = "$MountPoint\Software\Microsoft\SSMS\$FName\ConnectionMruList"
# make sure there's no leftover .reg file from a previous run
if (Test-Path -Path $RegFilePath) {
Remove-Item -Path $RegFilePath -Force
}
# Export the relevant key to a .reg file
Write-Host " Exporting $RegFileName..."
try {
Invoke-Reg -Command 'export' -Arguments @(
$ExportPath,
$RegFilePathSafe,
'/reg:64'
) -ErrorAction Stop
} catch {
Write-Warning "Failed to export $RegFileName" -Fore Red
# Unload the hive before continuing
Write-Host " Unloading hive and proceeding..."
Invoke-Reg -Command 'unload' -Arguments @($MountPoint)
continue
}
# Unload the hive
Write-Host " Unloading hive..."
Invoke-Reg -Command 'unload' -Arguments @($MountPoint)
$ConnStringCounter = 0
$CredentialsCounter = 0
$ConnStrings += "`n### Decrypted connections from $FName ###"
$Credentials += "`n### Decrypted credentials from $FName ###"
# Load the .reg file
$Connections = Select-String $RegFilePath -Pattern '"Connection\d' -Raw
if ( $Connections.Count -eq 0) {
# no need to continue if there's nothing to process
Write-Host " No saved connections found in $FName - skipping decryption." -Fore Yellow
# clean up .reg file unless told otherwise
if ((-not $KeepRegFiles) -and (Test-Path -Path $RegFilePath)) {
Remove-Item -Path $RegFilePath -Force
}
continue
} else {
Write-Host " Found $($Connections.Count) saved connections in $FName." -Fore Green
}
foreach ($Connection in $Connections) {
$ConnName = $Connection -replace "`"=.*", "" -replace '"', ''
$HexBlob = $Connection -replace '"Connection\d+"="', '' -replace '"', ''
if ([string]::IsNullOrWhiteSpace($HexBlob)) {
Write-Warning " $ConnName missing a blob - skipping."
continue
}
try {
# DPAPI decryption
$bytes = Convert-HexToByteArray -Hex $hexBlob
$plain = [System.Security.Cryptography.ProtectedData]::Unprotect(
$bytes,
$null,
[System.Security.Cryptography.DataProtectionScope]::CurrentUser)
$clearText = [Text.Encoding]::Unicode.GetString($plain)
# find stored credentials and format them nicely
if ($ClearText -like "*;Persist Security Info=True*") {
$CredentialsCounter++
$DataSource = $ClearText -replace ";Persist Security Info=True*.+", ""
$DataSource = $DataSource -replace ".*Data Source=", ""
$UserID = $ClearText -replace ".*User ID=", "User ID=" -replace ";Password*.+", ""
$Pass = $ClearText -replace ".*;Password=", "Password=" -replace ";Pooling=.*", ""
$Credentials += "## $DataSource `n $UserID `n $Pass"
}
$ConnStrings += "$clearText"
$ConnStringCounter++
} catch {
$msg = $_.Exception.Message
Write-Warning "Failed to decrypt ${ConnName}: $msg"
$ConnStrings += "${ConnName}: *** DECRYPTION FAILED ***"
}
}
Write-Host " Decrypted connection strings:" $ConnStringCounter -Fore Green
Write-Host " Decrypted credentials:" $CredentialsCounter -Fore Green
$TotalConnStrings += $ConnStringCounter
$TotalCredentials += $CredentialsCounter
# clean up .reg file unless told otherwise
if ((-not $KeepRegFiles) -and (Test-Path -Path $RegFilePath)) {
Remove-Item -Path $RegFilePath -Force
}
}
# write output files
$ConnStrings | Set-Content -Path $RawConnStringPath -Encoding Unicode
$Credentials | Set-Content -Path $CredentialsPath -Encoding Unicode
Write-Host "`n Decryption complete." -Fore Green
Write-Host " Total extracted connection strings: $TotalConnStrings" -Fore Green
Write-Host " Total extracted credentials: $TotalCredentials" -Fore Green